Merge branch '1.5.x' into feat-zoho-oauth
This commit is contained in:
commit
a74e5cc2c6
7
.env
7
.env
|
@ -87,7 +87,7 @@ _APP_LOGGING_PROVIDER=
|
|||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
_APP_GRAPHQL_MAX_DEPTH=4
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_VCS_GITHUB_APP_NAME=
|
||||
|
@ -98,4 +98,7 @@ _APP_VCS_GITHUB_CLIENT_SECRET=
|
|||
_APP_VCS_GITHUB_WEBHOOK_SECRET=
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
|
||||
_APP_ASSISTANT_OPENAI_API_KEY=
|
||||
_APP_ASSISTANT_OPENAI_API_KEY=
|
||||
_APP_MESSAGE_SMS_TEST_DSN=
|
||||
_APP_MESSAGE_EMAIL_TEST_DSN=
|
||||
_APP_MESSAGE_PUSH_TEST_DSN=
|
||||
|
|
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
|
@ -99,6 +99,7 @@ jobs:
|
|||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
Messaging,
|
||||
]
|
||||
|
||||
steps:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ debug/
|
|||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
Makefile
|
||||
|
|
|
@ -484,7 +484,7 @@
|
|||
## Features
|
||||
- Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357
|
||||
- Added Twilio Support
|
||||
- Added TextMagic Support
|
||||
- Added Textmagic Support
|
||||
- Added Telesign Support
|
||||
- Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`)
|
||||
- Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`)
|
||||
|
|
|
@ -5,7 +5,7 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
||||
/**
|
||||
|
@ -221,6 +221,17 @@ $commonCollections = [
|
|||
'array' => false,
|
||||
'filters' => ['subQueryMemberships'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targets'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryTargets'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
@ -1366,7 +1377,608 @@ $commonCollections = [
|
|||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
'providers' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('providers'),
|
||||
'name' => 'Providers',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('provider'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('enabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('credentials'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json', 'encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('options'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => ['providerSearch'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_provider'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['provider'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['type'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_enabled_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['enabled','type'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'messages' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('messages'),
|
||||
'name' => 'Messages',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('providerType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'processing',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('data'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('topics'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 21845,
|
||||
'signed' => true,
|
||||
'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' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduledAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveredAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveryErrors'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveredTotal'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => ['messageSearch'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'topics' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('topics'),
|
||||
'name' => 'Topics',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('total'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targets'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryTopicTargets'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => ['topicSearch'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'subscribers' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('subscribers'),
|
||||
'name' => 'Subscribers',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('targetId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targetInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('topicId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('topicInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_targetId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['targetId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_targetInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['targetInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_topicId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['topicId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_topicInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['topicInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'targets' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('targets'),
|
||||
'name' => 'Targets',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('identifier'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_userId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_identifier'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['identifier'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$projectCollections = array_merge([
|
||||
'databases' => [
|
||||
|
@ -3412,7 +4024,7 @@ $consoleCollections = array_merge([
|
|||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authProviders'),
|
||||
'$id' => ID::custom('oAuthProviders'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
|
|
|
@ -103,6 +103,16 @@ return [
|
|||
'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
|
||||
'code' => 405,
|
||||
],
|
||||
Exception::GENERAL_INVALID_EMAIL => [
|
||||
'name' => Exception::GENERAL_INVALID_EMAIL,
|
||||
'description' => 'Value must be a valid email address.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_INVALID_PHONE => [
|
||||
'name' => Exception::GENERAL_INVALID_PHONE,
|
||||
'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
|
@ -251,6 +261,16 @@ return [
|
|||
'description' => 'User phone is already verified',
|
||||
'code' => 409
|
||||
],
|
||||
Exception::USER_TARGET_NOT_FOUND => [
|
||||
'name' => Exception::USER_TARGET_NOT_FOUND,
|
||||
'description' => 'The target could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_TARGET_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_TARGET_ALREADY_EXISTS,
|
||||
'description' => 'A target with the same ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
|
@ -773,4 +793,83 @@ return [
|
|||
'code' => 503,
|
||||
'publish' => false
|
||||
],
|
||||
|
||||
/** Providers */
|
||||
Exception::PROVIDER_NOT_FOUND => [
|
||||
'name' => Exception::PROVIDER_NOT_FOUND,
|
||||
'description' => 'Provider with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PROVIDER_ALREADY_EXISTS => [
|
||||
'name' => Exception::PROVIDER_ALREADY_EXISTS,
|
||||
'description' => 'Provider with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PROVIDER_INCORRECT_TYPE => [
|
||||
'name' => Exception::PROVIDER_INCORRECT_TYPE,
|
||||
'description' => 'Provider with the requested ID is of incorrect type: ',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Topics */
|
||||
Exception::TOPIC_NOT_FOUND => [
|
||||
'name' => Exception::TOPIC_NOT_FOUND,
|
||||
'description' => 'Topic with the request ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TOPIC_ALREADY_EXISTS => [
|
||||
'name' => Exception::TOPIC_ALREADY_EXISTS,
|
||||
'description' => 'Topic with the request ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Subscribers */
|
||||
Exception::SUBSCRIBER_NOT_FOUND => [
|
||||
'name' => Exception::SUBSCRIBER_NOT_FOUND,
|
||||
'description' => 'Subscriber with the request ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::SUBSCRIBER_ALREADY_EXISTS => [
|
||||
'name' => Exception::SUBSCRIBER_ALREADY_EXISTS,
|
||||
'description' => 'Subscriber with the request ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Messages */
|
||||
Exception::MESSAGE_NOT_FOUND => [
|
||||
'name' => Exception::MESSAGE_NOT_FOUND,
|
||||
'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.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_SCHEDULED => [
|
||||
'name' => Exception::MESSAGE_ALREADY_SCHEDULED,
|
||||
'description' => 'Message with the requested ID has already been scheduled for delivery.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_EMAIL => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_EMAIL,
|
||||
'description' => 'Message with the target ID is not an email target:',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_SMS => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_SMS,
|
||||
'description' => 'Message with the target ID is not an SMS target:',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_PUSH => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_PUSH,
|
||||
'description' => 'Message with the target ID is not a push target:',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -44,6 +44,20 @@ return [
|
|||
'$description' => 'This event triggers when a verification token for a user is validated.'
|
||||
],
|
||||
],
|
||||
'targets' => [
|
||||
'$model' => Response::MODEL_TARGET,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s target event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a user\'s target is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a user\'s target is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a user\'s target is deleted.',
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a user is created.'
|
||||
],
|
||||
|
@ -237,6 +251,56 @@ return [
|
|||
'$description' => 'This event triggers when a function is updated.',
|
||||
]
|
||||
],
|
||||
'messages' => [
|
||||
'$model' => Response::MODEL_MESSAGE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any messaging event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a message is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a message is updated.',
|
||||
],
|
||||
],
|
||||
'topics' => [
|
||||
'$model' => Response::MODEL_TOPIC,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any topic event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a topic is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a topic is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a topic is deleted.'
|
||||
],
|
||||
'subscribers' => [
|
||||
'$model' => Response::MODEL_SUBSCRIBER,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any subscriber event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a subscriber is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a subscriber is deleted.'
|
||||
],
|
||||
],
|
||||
],
|
||||
'providers' => [
|
||||
'$model' => Response::MODEL_PROVIDER,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any provider event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a provider is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a provider is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a provider is deleted.'
|
||||
],
|
||||
],
|
||||
'rules' => [
|
||||
'$model' => Response::MODEL_PROXY_RULE,
|
||||
'$resource' => true,
|
||||
|
|
|
@ -21,6 +21,10 @@ $member = [
|
|||
'avatars.read',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'assistant.read',
|
||||
];
|
||||
|
||||
|
@ -60,6 +64,16 @@ $admins = [
|
|||
'migrations.write',
|
||||
'vcs.read',
|
||||
'vcs.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.write',
|
||||
'providers.read',
|
||||
'messages.write',
|
||||
'messages.read',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read'
|
||||
];
|
||||
|
||||
return [
|
||||
|
|
|
@ -76,6 +76,36 @@ return [ // List of publicly visible scopes
|
|||
'health.read' => [
|
||||
'description' => 'Access to read your project\'s health status',
|
||||
],
|
||||
'providers.read' => [
|
||||
'description' => 'Access to read your project\'s providers',
|
||||
],
|
||||
'providers.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s providers',
|
||||
],
|
||||
'messages.read' => [
|
||||
'description' => 'Access to read your project\'s messages',
|
||||
],
|
||||
'messages.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s messages',
|
||||
],
|
||||
'topics.read' => [
|
||||
'description' => 'Access to read your project\'s topics',
|
||||
],
|
||||
'topics.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s topics',
|
||||
],
|
||||
'subscribers.read' => [
|
||||
'description' => 'Access to read your project\'s subscribers',
|
||||
],
|
||||
'subscribers.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s subscribers',
|
||||
],
|
||||
'targets.read' => [
|
||||
'description' => 'Access to read your project\'s targets',
|
||||
],
|
||||
'targets.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s targets',
|
||||
],
|
||||
'rules.read' => [
|
||||
'description' => 'Access to read your project\'s proxy rules',
|
||||
],
|
||||
|
|
|
@ -251,4 +251,17 @@ return [
|
|||
'optional' => false,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
],
|
||||
'messaging' => [
|
||||
'key' => 'messaging',
|
||||
'name' => 'Messaging',
|
||||
'subtitle' => 'The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).',
|
||||
'description' => '/docs/services/messaging.md',
|
||||
'controller' => 'api/messaging.php',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/server/messaging',
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/messaging.png',
|
||||
]
|
||||
];
|
||||
|
|
|
@ -440,7 +440,7 @@ return [
|
|||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_SMS_PROVIDER',
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.",
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, Textmagic, telesign, msg91, and vonage.",
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
|
|
@ -8,7 +8,6 @@ use Appwrite\Auth\Validator\Phone;
|
|||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
|
@ -45,6 +44,7 @@ use Utopia\Validator\WhiteList;
|
|||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
use Appwrite\Auth\Validator\PersonalData;
|
||||
use Appwrite\Event\Messaging;
|
||||
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
|
@ -69,7 +69,7 @@ App::post('/v1/account')
|
|||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), 'Unique 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('email', '', new Email(), 'User email.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
@ -148,7 +148,22 @@ App::post('/v1/account')
|
|||
'accessedAt' => DateTime::now(),
|
||||
]);
|
||||
$user->removeAttribute('$internalId');
|
||||
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
'identifier' => $email,
|
||||
])));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -299,7 +314,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
|||
->label('sdk.methodType', 'webAuth')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), fn($node) => (!$node['mock'])))) . '.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
|
@ -311,14 +326,14 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
|||
$protocol = $request->getProtocol();
|
||||
|
||||
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
||||
if (!$providerEnabled) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
|
||||
}
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
if (!empty($appSecret) && isset($appSecret['version'])) {
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
|
||||
|
@ -358,7 +373,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new Text(1024), 'Project ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'Login state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
|
@ -391,7 +406,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
|||
->label('origin', '*')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new Text(1024), 'Project ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'Login state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
|
@ -430,7 +445,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
->label('docs', false)
|
||||
->label('usage.metric', 'sessions.{scope}.requests.create')
|
||||
->label('usage.params', ['provider:{request.provider}'])
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'OAuth2 state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
|
@ -448,9 +463,9 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
|
||||
$validateURL = new URL();
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
|
@ -458,7 +473,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$providers = Config::getParam('providers');
|
||||
$providers = Config::getParam('oAuthProviders');
|
||||
$providerName = $providers[$provider]['name'] ?? '';
|
||||
|
||||
/** @var Appwrite\Auth\OAuth2 $oauth2 */
|
||||
|
@ -664,7 +679,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'accessedAt' => DateTime::now(),
|
||||
]);
|
||||
$user->removeAttribute('$internalId');
|
||||
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
$userDoc = Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
$dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
],
|
||||
'userId' => $userDoc->getId(),
|
||||
'userInternalId' => $userDoc->getInternalId(),
|
||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
'identifier' => $email,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
$failureRedirect(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -1243,8 +1269,7 @@ App::post('/v1/account/sessions/phone')
|
|||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('locale')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, EventPhone $queueForMessaging, Locale $locale) {
|
||||
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) {
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
@ -1294,6 +1319,21 @@ App::post('/v1/account/sessions/phone')
|
|||
|
||||
$user->removeAttribute('$internalId');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => MESSAGE_TYPE_SMS,
|
||||
'identifier' => $phone,
|
||||
])));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
}
|
||||
|
||||
$secret = Auth::codeGenerator();
|
||||
|
@ -1331,9 +1371,19 @@ App::post('/v1/account/sessions/phone')
|
|||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $token->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$queueForEvents->setPayload(
|
||||
|
@ -1652,6 +1702,81 @@ App::post('/v1/account/jwt')
|
|||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
App::post('/v1/account/targets/push')
|
||||
->desc('Create Account\'s push target')
|
||||
->groups(['api', 'account'])
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('audits.event', 'target.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
->label('event', 'users.[userId].targets.[targetId].create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createPushTarget')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->label('docs', false)
|
||||
->param('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->inject('queueForEvents')
|
||||
->inject('user')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $providerId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
|
||||
|
||||
$provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if (!$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$detector = new Detector($request->getUserAgent());
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$device = $detector->getDevice();
|
||||
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'$id' => $targetId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
],
|
||||
'providerId' => $providerId ?? null,
|
||||
'providerInternalId' => $provider->getInternalId() ?? null,
|
||||
'providerType' => MESSAGE_TYPE_PUSH,
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'identifier' => $identifier,
|
||||
'name' => "{$device['deviceBrand']} {$device['deviceModel']}"
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/account')
|
||||
->desc('Get account')
|
||||
->groups(['api', 'account'])
|
||||
|
@ -1977,6 +2102,7 @@ App::patch('/v1/account/email')
|
|||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$oldEmail = $user->getAttribute('email');
|
||||
$email = \strtolower($email);
|
||||
|
||||
// Makes sure this email is not already used in another identity
|
||||
|
@ -2001,8 +2127,25 @@ App::patch('/v1/account/email')
|
|||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]));
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -2047,6 +2190,16 @@ App::patch('/v1/account/phone')
|
|||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]));
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$oldPhone = $user->getAttribute('phone');
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $phone)
|
||||
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
|
||||
|
@ -2062,6 +2215,15 @@ App::patch('/v1/account/phone')
|
|||
|
||||
try {
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -2272,8 +2434,8 @@ App::patch('/v1/account/sessions/:sessionId')
|
|||
$provider = $session->getAttribute('provider');
|
||||
$refreshToken = $session->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
|
@ -2570,8 +2732,8 @@ App::put('/v1/account/recovery')
|
|||
->label('abuse-key', 'url:{url},userId:{param-userId}')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('secret', '', new Text(256), 'Valid reset token.')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('passwordAgain', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Repeat new user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
|
@ -2885,10 +3047,9 @@ App::post('/v1/account/verification/phone')
|
|||
->inject('queueForMessaging')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, EventPhone $queueForMessaging, Document $project, Locale $locale) {
|
||||
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) {
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED);
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('phone'))) {
|
||||
|
@ -2937,11 +3098,19 @@ App::post('/v1/account/verification/phone')
|
|||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $verification->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setRecipient($user->getAttribute('phone'))
|
||||
->setMessage($message)
|
||||
->trigger()
|
||||
;
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
|
@ -3018,3 +3187,61 @@ App::put('/v1/account/verification/phone')
|
|||
|
||||
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::put('/v1/account/targets/:targetId/push')
|
||||
->desc('Update Account\'s push target')
|
||||
->groups(['api', 'account'])
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('audits.event', 'target.update')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
->label('event', 'users.[userId].targets.[targetId].update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePushTarget')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->label('docs', false)
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->inject('queueForEvents')
|
||||
->inject('user')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getId() !== $target->getAttribute('userId')) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($identifier) {
|
||||
$target->setAttribute('identifier', $identifier);
|
||||
}
|
||||
|
||||
$detector = new Detector($request->getUserAgent());
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$target->setAttribute('name', "{$device['deviceBrand']} {$device['deviceModel']}");
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
|
|
@ -84,8 +84,8 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
|||
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
|
||||
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
|
|
|
@ -1561,7 +1561,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
|
|||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required.', true)
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
2961
app/controllers/api/messaging.php
Normal file
2961
app/controllers/api/messaging.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -159,7 +159,7 @@ App::post('/v1/projects')
|
|||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'authProviders' => [],
|
||||
'oAuthProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
|
@ -598,7 +598,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
|
@ -612,7 +612,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$providers = $project->getAttribute('authProviders', []);
|
||||
$providers = $project->getAttribute('oAuthProviders', []);
|
||||
|
||||
if ($appId !== null) {
|
||||
$providers[$provider . 'Appid'] = $appId;
|
||||
|
@ -626,7 +626,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
$providers[$provider . 'Enabled'] = $enabled;
|
||||
}
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('oAuthProviders', $providers));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
@ -1151,7 +1151,7 @@ App::post('/v1/projects/:projectId/keys')
|
|||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
@ -1268,7 +1268,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
|||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
|
|
@ -6,7 +6,7 @@ use Appwrite\Detector\Detector;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
|
@ -388,7 +388,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, EventPhone $queueForMessaging, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
|
@ -628,6 +628,10 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-' . $locale->default] ?? [];
|
||||
|
@ -638,9 +642,18 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$message = $message->setParam('{{token}}', $url);
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS')
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use Appwrite\Event\Event;
|
|||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Identities;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
|
@ -98,6 +99,42 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $phone, $name]),
|
||||
]));
|
||||
|
||||
if ($email) {
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => 'email',
|
||||
'identifier' => $email,
|
||||
]));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($phone) {
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => 'sms',
|
||||
'identifier' => $phone,
|
||||
]));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -379,6 +416,98 @@ App::post('/v1/users/scrypt-modified')
|
|||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/targets')
|
||||
->desc('Create User Target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
->label('event', 'users.[userId].targets.[targetId].create')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'createTarget')
|
||||
->label('sdk.description', '/docs/references/users/create-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
|
||||
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
|
||||
|
||||
$provider = new Document();
|
||||
|
||||
if ($providerType === MESSAGE_TYPE_PUSH) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_SMS:
|
||||
$validator = new Phone();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_PHONE);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_PUSH:
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if (!$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'$id' => $targetId,
|
||||
'providerId' => $providerId ?? null,
|
||||
'providerInternalId' => $provider->getInternalId() ?? null,
|
||||
'providerType' => $providerType,
|
||||
'userId' => $userId,
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'identifier' => $identifier,
|
||||
'name' => ($name !== '') ? $name : null,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users')
|
||||
->desc('List users')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -482,6 +611,38 @@ App::get('/v1/users/:userId/prefs')
|
|||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get User Target')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'getTarget')
|
||||
->label('sdk.description', '/docs/references/users/get-user-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $user->find('$id', $targetId, 'targets');
|
||||
|
||||
if (empty($target)) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/sessions')
|
||||
->desc('List user sessions')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -646,6 +807,53 @@ App::get('/v1/users/:userId/logs')
|
|||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets')
|
||||
->desc('List User Targets')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'listTargets')
|
||||
->label('sdk.description', '/docs/references/users/list-user-targets.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET_LIST)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('queries', [], new Targets(), '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(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
$queries[] = Query::equal('userId', [$userId]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$targetId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'targets' => $dbForProject->find('targets', $queries),
|
||||
'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/identities')
|
||||
->desc('List Identities')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -949,6 +1157,16 @@ App::patch('/v1/users/:userId/email')
|
|||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$oldEmail = $user->getAttribute('email');
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
|
@ -957,6 +1175,15 @@ App::patch('/v1/users/:userId/email')
|
|||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -994,13 +1221,32 @@ App::patch('/v1/users/:userId/phone')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$oldPhone = $user->getAttribute('phone');
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phoneVerification', false)
|
||||
;
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$number]),
|
||||
]);
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -1080,6 +1326,100 @@ App::patch('/v1/users/:userId/prefs')
|
|||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update User target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.update')
|
||||
->label('audits.resource', 'target/{response.$id}')
|
||||
->label('event', 'users.[userId].targets.[targetId].update')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updateTarget')
|
||||
->label('sdk.description', '/docs/references/users/update-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
|
||||
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
|
||||
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getId() !== $target->getAttribute('userId')) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($identifier) {
|
||||
$providerType = $target->getAttribute('providerType');
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_SMS:
|
||||
$validator = new Phone();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_PHONE);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_PUSH:
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('identifier', $identifier);
|
||||
}
|
||||
|
||||
if ($providerId) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($provider->getAttribute('type') !== $target->getAttribute('providerType')) {
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('providerId', $provider->getId());
|
||||
$target->setAttribute('providerInternalId', $provider->getInternalId());
|
||||
}
|
||||
|
||||
if ($name) {
|
||||
$target->setAttribute('name', $name);
|
||||
}
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete user session')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -1210,6 +1550,53 @@ App::delete('/v1/users/:userId')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Delete user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.delete')
|
||||
->label('audits.resource', 'target/{request.$targetId}')
|
||||
->label('event', 'users.[userId].targets.[targetId].delete')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'deleteTarget')
|
||||
->label('sdk.description', '/docs/references/users/delete-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getId() !== $target->getAttribute('userId')) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('targets', $target->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/identities/:identityId')
|
||||
->desc('Delete Identity')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -1251,7 +1638,7 @@ App::get('/v1/users/usage')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
|
||||
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('oAuthProviders', [])))), true), 'Provider Name.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
|
|
137
app/init.php
137
app/init.php
|
@ -25,7 +25,7 @@ use Appwrite\Event\Audit;
|
|||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
|
@ -47,13 +47,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
|||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
use Utopia\Messaging\Adapters\SMS\TextMagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
|
@ -83,6 +77,7 @@ use Utopia\Validator\Range;
|
|||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
@ -103,6 +98,7 @@ const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
|||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1000000;
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
|
@ -173,6 +169,7 @@ const DELETE_TYPE_SESSIONS = 'sessions';
|
|||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
const DELETE_TYPE_SCHEDULES = 'schedules';
|
||||
const DELETE_TYPE_TOPIC = 'topic';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
|
@ -189,6 +186,10 @@ const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
|||
// Function headers
|
||||
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
|
||||
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Message types
|
||||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
const MESSAGE_TYPE_SMS = 'sms';
|
||||
const MESSAGE_TYPE_PUSH = 'push';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
|
@ -233,7 +234,7 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
|||
Config::load('events', __DIR__ . '/config/events.php');
|
||||
Config::load('auth', __DIR__ . '/config/auth.php');
|
||||
Config::load('errors', __DIR__ . '/config/errors.php');
|
||||
Config::load('providers', __DIR__ . '/config/providers.php');
|
||||
Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
|
||||
Config::load('platforms', __DIR__ . '/config/platforms.php');
|
||||
Config::load('collections', __DIR__ . '/config/collections.php');
|
||||
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
|
||||
|
@ -522,6 +523,107 @@ Database::addFilter(
|
|||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTargets',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTopicTargets',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetId'),
|
||||
$database
|
||||
->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
|
||||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->find('targets', [Query::equal('$id', $targetIds)]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'providerSearch',
|
||||
function (mixed $value, Document $provider) {
|
||||
$searchValues = [
|
||||
$provider->getId(),
|
||||
$provider->getAttribute('name', ''),
|
||||
$provider->getAttribute('provider', ''),
|
||||
$provider->getAttribute('type', '')
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'topicSearch',
|
||||
function (mixed $value, Document $topic) {
|
||||
$searchValues = [
|
||||
$topic->getId(),
|
||||
$topic->getAttribute('name', ''),
|
||||
$topic->getAttribute('description', ''),
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'messageSearch',
|
||||
function (mixed $value, Document $message) {
|
||||
$searchValues = [
|
||||
$message->getId(),
|
||||
$message->getAttribute('description', ''),
|
||||
$message->getAttribute('status', ''),
|
||||
];
|
||||
|
||||
$data = \json_decode($message->getAttribute('data', []), true);
|
||||
$providerType = $message->getAttribute('providerType', '');
|
||||
|
||||
if ($providerType === MESSAGE_TYPE_EMAIL) {
|
||||
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
|
||||
} elseif ($providerType === MESSAGE_TYPE_SMS) {
|
||||
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
|
||||
} else {
|
||||
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
|
||||
}
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* DB Formats
|
||||
*/
|
||||
|
@ -902,7 +1004,7 @@ App::setResource('queue', function (Group $pools) {
|
|||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
App::setResource('queueForMessaging', function (Connection $queue) {
|
||||
return new Phone($queue);
|
||||
return new Messaging($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForMails', function (Connection $queue) {
|
||||
return new Mail($queue);
|
||||
|
@ -1118,7 +1220,7 @@ App::setResource('console', function () {
|
|||
],
|
||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
'authProviders' => [
|
||||
'oAuthProviders' => [
|
||||
'githubEnabled' => true,
|
||||
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||
|
@ -1342,21 +1444,6 @@ App::setResource('passwordsDictionary', function ($register) {
|
|||
return $register->get('passwordsDictionary');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('sms', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
return match ($dsn->getHost()) {
|
||||
'mock' => new Mock($user, $secret), // used for tests
|
||||
'twilio' => new Twilio($user, $secret),
|
||||
'text-magic' => new TextMagic($user, $secret),
|
||||
'telesign' => new Telesign($user, $secret),
|
||||
'msg91' => new Msg91($user, $secret),
|
||||
'vonage' => new Vonage($user, $secret),
|
||||
default => null
|
||||
};
|
||||
});
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
|
|
|
@ -521,14 +521,20 @@ services:
|
|||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
||||
appwrite-worker-migrations:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
|
|
@ -13,7 +13,6 @@ use Appwrite\Event\Hamster;
|
|||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Swoole\Runtime;
|
||||
|
@ -130,7 +129,7 @@ Server::setResource('queueForDatabase', function (Connection $queue) {
|
|||
return new EventDatabase($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForMessaging', function (Connection $queue) {
|
||||
return new Phone($queue);
|
||||
return new Messaging($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForMails', function (Connection $queue) {
|
||||
return new Mail($queue);
|
||||
|
|
|
@ -188,7 +188,9 @@ services:
|
|||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
- _APP_MESSAGE_SMS_TEST_DSN
|
||||
- _APP_MESSAGE_EMAIL_TEST_DSN
|
||||
- _APP_MESSAGE_PUSH_TEST_DSN
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
<<: *x-logging
|
||||
|
@ -568,14 +570,20 @@ services:
|
|||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
||||
appwrite-worker-migrations:
|
||||
entrypoint: worker-migrations
|
||||
|
|
|
@ -37,7 +37,7 @@ Finally, you will need to create a `feat-XXX-YYY-oauth` branch based on the `mas
|
|||
The first step in adding a new OAuth2 provider is to add it to the list of providers located at:
|
||||
|
||||
```
|
||||
app/config/providers.php
|
||||
app/config/oAuthProviders.php
|
||||
```
|
||||
|
||||
Make sure to fill in all data needed and that your provider array key name:
|
||||
|
@ -45,7 +45,7 @@ Make sure to fill in all data needed and that your provider array key name:
|
|||
- is in [`camelCase`](https://en.wikipedia.org/wiki/Camel_case) format for sentence, but lowercase for names. `github` must be all lowercased, but `paypalSandbox` should have uppercase S
|
||||
- has no spaces or special characters
|
||||
|
||||
> Please make sure to keep the list of providers in `providers.php` in the alphabetical order A-Z.
|
||||
> Please make sure to keep the list of providers in `oAuthProviders.php` in the alphabetical order A-Z.
|
||||
|
||||
### 2.2 Add Provider Logo
|
||||
|
||||
|
@ -199,7 +199,7 @@ If you need any help with the contribution, feel free to head over to [our Disco
|
|||
|
||||
If your OAuth provider requires special configuration apart from `clientId` and `clientSecret` you can create a custom form. Currently this is being realized through putting all custom fields as JSON into the `clientSecret` field to keep the project API stable. You can implement your custom form following these steps:
|
||||
|
||||
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/providers.php`.
|
||||
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/oAuthProviders.php`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<directory>./tests/e2e/Services/Projects</directory>
|
||||
<directory>./tests/e2e/Services/Storage</directory>
|
||||
<directory>./tests/e2e/Services/Webhooks</directory>
|
||||
<directory>./tests/e2e/Services/Messaging</directory>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
|
||||
|
|
|
@ -20,7 +20,7 @@ class Password extends Validator
|
|||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password must be at least 8 characters';
|
||||
return 'Password must be between 8 and 256 characters long.';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +40,10 @@ class Password extends Validator
|
|||
return false;
|
||||
}
|
||||
|
||||
if (\strlen($value) > 256) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class PasswordDictionary extends Password
|
|||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password must be at least 8 characters and should not be one of the commonly used password.';
|
||||
return 'Password must be between 8 and 265 characters long, and should not be one of the commonly used password.';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
173
src/Appwrite/Event/Messaging.php
Normal file
173
src/Appwrite/Event/Messaging.php
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Client;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
protected ?string $messageId = null;
|
||||
protected ?Document $message = null;
|
||||
protected ?array $recipients = null;
|
||||
protected ?string $scheduledAt = null;
|
||||
protected ?string $providerType = null;
|
||||
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MESSAGING_QUEUE_NAME)
|
||||
->setClass(Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string[] $recipients
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipients(array $recipients): self
|
||||
{
|
||||
$this->recipients = $recipients;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for messaging event.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRecipient(): array
|
||||
{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message document for the messaging event.
|
||||
*
|
||||
* @param Document $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(Document $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message document for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): Document
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message ID for the messaging event.
|
||||
*
|
||||
* @param string $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessageId(string $messageId): self
|
||||
{
|
||||
$this->messageId = $messageId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set message ID for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageId(): string
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets provider type for the messaging event.
|
||||
*
|
||||
* @param string $providerType
|
||||
* @return self
|
||||
*/
|
||||
public function setProviderType(string $providerType): self
|
||||
{
|
||||
$this->providerType = $providerType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set provider type for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProviderType(): string
|
||||
{
|
||||
return $this->providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Scheduled delivery time for the messaging event.
|
||||
*
|
||||
* @param string $scheduledAt
|
||||
* @return self
|
||||
*/
|
||||
public function setScheduledAt(string $scheduledAt): self
|
||||
{
|
||||
$this->scheduledAt = $scheduledAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScheduledAt(): string
|
||||
{
|
||||
return $this->scheduledAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set project for this event.
|
||||
*
|
||||
* @param Document $project
|
||||
* @return self
|
||||
*/
|
||||
public function setProject(Document $project): self
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string | bool
|
||||
{
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message,
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Phone extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $message = '';
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MESSAGING_QUEUE_NAME)
|
||||
->setClass(Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string $recipient
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipient(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for this messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecipient(): string
|
||||
{
|
||||
return $this->recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets url for the messaging event.
|
||||
*
|
||||
* @param string $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(string $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set url for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'payload' => $this->payload,
|
||||
'recipient' => $this->recipient,
|
||||
'message' => $this->message,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -55,6 +55,8 @@ class Exception extends \Exception
|
|||
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
|
||||
public const GENERAL_USAGE_DISABLED = 'general_usage_disabled';
|
||||
public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented';
|
||||
public const GENERAL_INVALID_EMAIL = 'general_invalid_email';
|
||||
public const GENERAL_INVALID_PHONE = 'general_invalid_phone';
|
||||
|
||||
/** Users */
|
||||
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
|
@ -86,6 +88,8 @@ class Exception extends \Exception
|
|||
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
|
||||
public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified';
|
||||
public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified';
|
||||
public const USER_TARGET_NOT_FOUND = 'user_target_not_found';
|
||||
public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
|
||||
|
||||
/** Teams */
|
||||
public const TEAM_NOT_FOUND = 'team_not_found';
|
||||
|
@ -236,6 +240,30 @@ class Exception extends \Exception
|
|||
/** Health */
|
||||
public const QUEUE_SIZE_EXCEEDED = 'queue_size_exceeded';
|
||||
|
||||
/** Provider */
|
||||
public const PROVIDER_NOT_FOUND = 'provider_not_found';
|
||||
public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists';
|
||||
public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type';
|
||||
public const PROVIDER_INTERNAL_UPDATE_DISABLED = 'provider_internal_update_disabled';
|
||||
|
||||
/** Topic */
|
||||
public const TOPIC_NOT_FOUND = 'topic_not_found';
|
||||
public const TOPIC_ALREADY_EXISTS = 'topic_already_exists';
|
||||
|
||||
/** Subscriber */
|
||||
public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found';
|
||||
public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists';
|
||||
|
||||
/** 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';
|
||||
public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email';
|
||||
public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms';
|
||||
public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push';
|
||||
|
||||
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
protected bool $publish = true;
|
||||
|
|
|
@ -34,7 +34,7 @@ class V15 extends Migration
|
|||
['email', 'anonymous'],
|
||||
\array_map(
|
||||
fn ($value) => "oauth-" . $value,
|
||||
\array_keys(Config::getParam('providers', []))
|
||||
\array_keys(Config::getParam('oAuthProviders', []))
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -124,23 +124,23 @@ class V16 extends Migration
|
|||
/**
|
||||
* Enable OAuth providers with data
|
||||
*/
|
||||
$authProviders = $document->getAttribute('authProviders', []);
|
||||
$oAuthProviders = $document->getAttribute('oAuthProviders', []);
|
||||
|
||||
foreach (Config::getParam('providers') as $provider => $value) {
|
||||
foreach (Config::getParam('oAuthProviders') as $provider => $value) {
|
||||
if (!$value['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($authProviders[$provider . 'Appid'] ?? false) && ($authProviders[$provider . 'Secret'] ?? false)) {
|
||||
if (array_key_exists($provider . 'Enabled', $authProviders)) {
|
||||
if (($oAuthProviders[$provider . 'Appid'] ?? false) && ($oAuthProviders[$provider . 'Secret'] ?? false)) {
|
||||
if (array_key_exists($provider . 'Enabled', $oAuthProviders)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$authProviders[$provider . 'Enabled'] = true;
|
||||
$oAuthProviders[$provider . 'Enabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$document->setAttribute('authProviders', $authProviders);
|
||||
$document->setAttribute('oAuthProviders', $oAuthProviders);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Executor\Executor;
|
||||
use Throwable;
|
||||
use Utopia\Abuse\Abuse;
|
||||
|
@ -149,6 +150,9 @@ class Deletes extends Action
|
|||
case DELETE_TYPE_SCHEDULES:
|
||||
$this->deleteSchedules($dbForConsole, $getProjectDB, $datetime);
|
||||
break;
|
||||
case DELETE_TYPE_TOPIC:
|
||||
$this->deleteTopic($project, $getProjectDB, $document);
|
||||
break;
|
||||
default:
|
||||
Console::error('No delete operation for type: ' . $type);
|
||||
break;
|
||||
|
@ -193,6 +197,25 @@ class Deletes extends Action
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $topic
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteTopic(Document $project, callable $getProjectDB, Document $topic)
|
||||
{
|
||||
if ($topic->isEmpty()) {
|
||||
Console::error('Failed to delete subscribers. Topic not found');
|
||||
return;
|
||||
}
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$this->deleteByGroup('subscribers', [
|
||||
Query::equal('topicInternalId', [$topic->getInternalId()])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
|
@ -533,6 +556,11 @@ class Deletes extends Action
|
|||
$this->deleteByGroup('identities', [
|
||||
Query::equal('userInternalId', [$userInternalId])
|
||||
], $dbForProject);
|
||||
|
||||
// Delete targets
|
||||
$this->deleteByGroup('targets', [
|
||||
Query::equal('userInternalId', [$userInternalId])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,30 +2,41 @@
|
|||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Exception;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Messaging\Messages\SMS;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Messaging\Adapters\SMS as SMSAdapter;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
use Utopia\Messaging\Adapters\SMS\TextMagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Textmagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Messaging\Adapters\Push as PushAdapter;
|
||||
use Utopia\Messaging\Adapters\Push\APNS;
|
||||
use Utopia\Messaging\Adapters\Push\FCM;
|
||||
use Utopia\Messaging\Adapters\Email as EmailAdapter;
|
||||
use Utopia\Messaging\Adapters\Email\Mailgun;
|
||||
use Utopia\Messaging\Adapters\Email\SendGrid;
|
||||
use Utopia\Messaging\Messages\Email;
|
||||
use Utopia\Messaging\Messages\Push;
|
||||
use Utopia\Messaging\Messages\SMS;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
class Messaging extends Action
|
||||
{
|
||||
private ?DSN $dsn = null;
|
||||
private string $user = '';
|
||||
private string $secret = '';
|
||||
private string $provider = '';
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'messaging';
|
||||
return "messaging";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,25 +44,20 @@ class Messaging extends Action
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->provider = App::getEnv('_APP_SMS_PROVIDER', '');
|
||||
if (!empty($this->provider)) {
|
||||
$this->dsn = new DSN($this->provider);
|
||||
$this->user = $this->dsn->getUser();
|
||||
$this->secret = $this->dsn->getPassword();
|
||||
}
|
||||
|
||||
$this
|
||||
->desc('Messaging worker')
|
||||
->inject('message')
|
||||
->callback(fn($message) => $this->action($message));
|
||||
->inject('dbForProject')
|
||||
->callback(fn(Message $message, Database $dbForProject) => $this->action($message, $dbForProject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Database $dbForProject
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message): void
|
||||
public function action(Message $message, Database $dbForProject): void
|
||||
{
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
|
@ -60,48 +66,318 @@ class Messaging extends Action
|
|||
return;
|
||||
}
|
||||
|
||||
if (empty($payload['recipient'])) {
|
||||
Console::error('Recipient arg not found');
|
||||
if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) {
|
||||
if ($payload['providerType'] === MESSAGE_TYPE_SMS) {
|
||||
$this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']);
|
||||
}
|
||||
} else {
|
||||
$message = $dbForProject->getDocument('messages', $payload['messageId']);
|
||||
|
||||
$this->processMessage($dbForProject, $message);
|
||||
}
|
||||
}
|
||||
|
||||
private function processMessage(Database $dbForProject, Document $message): void
|
||||
{
|
||||
$topicsId = $message->getAttribute('topics', []);
|
||||
$targetsId = $message->getAttribute('targets', []);
|
||||
$usersId = $message->getAttribute('users', []);
|
||||
|
||||
/**
|
||||
* @var Document[] $recipients
|
||||
*/
|
||||
$recipients = [];
|
||||
|
||||
if (\count($topicsId) > 0) {
|
||||
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
|
||||
foreach ($topics as $topic) {
|
||||
$targets = \array_filter($topic->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($usersId) > 0) {
|
||||
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
|
||||
foreach ($users as $user) {
|
||||
$targets = \array_filter($user->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($targetsId) > 0) {
|
||||
$targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]);
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
|
||||
$primaryProvider = $dbForProject->findOne('providers', [
|
||||
Query::equal('enabled', [true]),
|
||||
Query::equal('type', [$recipients[0]->getAttribute('providerType')]),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @var array<string, array<string>> $identifiersByProviderId
|
||||
*/
|
||||
$identifiersByProviderId = [];
|
||||
|
||||
/**
|
||||
* @var Document[] $providers
|
||||
*/
|
||||
$providers = [];
|
||||
foreach ($recipients as $recipient) {
|
||||
$providerId = $recipient->getAttribute('providerId');
|
||||
|
||||
if (!$providerId && $primaryProvider instanceof Document && !$primaryProvider->isEmpty()) {
|
||||
$providerId = $primaryProvider->getId();
|
||||
}
|
||||
|
||||
if ($providerId) {
|
||||
if (!isset($identifiersByProviderId[$providerId])) {
|
||||
$identifiersByProviderId[$providerId] = [];
|
||||
}
|
||||
$identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array[] $results
|
||||
*/
|
||||
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
|
||||
return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
|
||||
$provider = new Document();
|
||||
|
||||
if ($primaryProvider->getId() === $providerId) {
|
||||
$provider = $primaryProvider;
|
||||
} else {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
$provider = $primaryProvider;
|
||||
}
|
||||
}
|
||||
|
||||
$providers[] = $provider;
|
||||
$identifiers = $identifiersByProviderId[$providerId];
|
||||
|
||||
$adapter = match ($provider->getAttribute('type')) {
|
||||
MESSAGE_TYPE_SMS => $this->sms($provider),
|
||||
MESSAGE_TYPE_PUSH => $this->push($provider),
|
||||
MESSAGE_TYPE_EMAIL => $this->email($provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
|
||||
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
|
||||
$batches = \array_chunk($identifiers, $maxBatchSize);
|
||||
$batchIndex = 0;
|
||||
|
||||
$results = batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
|
||||
$deliveredTotal = 0;
|
||||
$deliveryErrors = [];
|
||||
$messageData = clone $message;
|
||||
$messageData->setAttribute('to', $batch);
|
||||
|
||||
$data = match ($provider->getAttribute('type')) {
|
||||
MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider),
|
||||
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
|
||||
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
$deliveredTotal += \count($batch);
|
||||
} catch (\Exception $e) {
|
||||
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
|
||||
} finally {
|
||||
$batchIndex++;
|
||||
return [
|
||||
'deliveredTotal' => $deliveredTotal,
|
||||
'deliveryErrors' => $deliveryErrors,
|
||||
];
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
|
||||
return $results;
|
||||
};
|
||||
}, \array_keys($identifiersByProviderId)));
|
||||
|
||||
$results = array_merge(...$results);
|
||||
|
||||
$deliveredTotal = 0;
|
||||
$deliveryErrors = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$deliveredTotal += $result['deliveredTotal'];
|
||||
$deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']);
|
||||
}
|
||||
|
||||
$message->setAttribute('deliveryErrors', $deliveryErrors);
|
||||
|
||||
if (\count($message->getAttribute('deliveryErrors')) > 0) {
|
||||
$message->setAttribute('status', 'failed');
|
||||
} else {
|
||||
$message->setAttribute('status', 'sent');
|
||||
}
|
||||
|
||||
$message->removeAttribute('to');
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
$message->setAttribute('search', "{$message->getAttribute('search')} {$provider->getAttribute('name')} {$provider->getAttribute('provider')} {$provider->getAttribute('type')}");
|
||||
}
|
||||
|
||||
$message->setAttribute('deliveredTotal', $deliveredTotal);
|
||||
$message->setAttribute('deliveredAt', DateTime::now());
|
||||
|
||||
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
}
|
||||
|
||||
private function processInternalSMSMessage(Document $message, array $recipients): void
|
||||
{
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) {
|
||||
Console::info('Skipped SMS processing. No Phone configuration has been set.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($payload['message'])) {
|
||||
Console::error('Message arg not found');
|
||||
return;
|
||||
}
|
||||
|
||||
$sms = match ($this->dsn->getHost()) {
|
||||
'mock' => new Mock($this->user, $this->secret), // used for tests
|
||||
'twilio' => new Twilio($this->user, $this->secret),
|
||||
'text-magic' => new TextMagic($this->user, $this->secret),
|
||||
'telesign' => new Telesign($this->user, $this->secret),
|
||||
'msg91' => new Msg91($this->user, $this->secret),
|
||||
'vonage' => new Vonage($this->user, $this->secret),
|
||||
default => null
|
||||
};
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
Console::error('Skipped sms processing. No Phone provider has been set.');
|
||||
return;
|
||||
}
|
||||
$smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$host = $smsDSN->getHost();
|
||||
$password = $smsDSN->getPassword();
|
||||
$user = $smsDSN->getUser();
|
||||
|
||||
$from = App::getEnv('_APP_SMS_FROM');
|
||||
|
||||
if (empty($from)) {
|
||||
Console::error('Skipped sms processing. No phone number has been set.');
|
||||
return;
|
||||
}
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'username' => $user,
|
||||
'password' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => [
|
||||
'from' => $from
|
||||
]
|
||||
]);
|
||||
|
||||
$message = new SMS(
|
||||
to: [$payload['recipient']],
|
||||
content: $payload['message'],
|
||||
from: $from,
|
||||
);
|
||||
$adapter = $this->sms($provider);
|
||||
|
||||
try {
|
||||
$sms->send($message);
|
||||
} catch (\Exception $error) {
|
||||
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
|
||||
}
|
||||
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
|
||||
$batches = \array_chunk($recipients, $maxBatchSize);
|
||||
$batchIndex = 0;
|
||||
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
|
||||
$message->setAttribute('to', $batch);
|
||||
|
||||
$data = $this->buildSMSMessage($message, $provider);
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
} catch (\Exception $e) {
|
||||
Console::error('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage());
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
}
|
||||
|
||||
private function sms(Document $provider): ?SMSAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mock' => new Mock('username', 'password'),
|
||||
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
|
||||
'textmagic' => new Textmagic($credentials['username'], $credentials['apiKey']),
|
||||
'telesign' => new Telesign($credentials['username'], $credentials['password']),
|
||||
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']),
|
||||
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function push(Document $provider): ?PushAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'apns' => new APNS(
|
||||
$credentials['authKey'],
|
||||
$credentials['authKeyId'],
|
||||
$credentials['teamId'],
|
||||
$credentials['bundleId'],
|
||||
$credentials['endpoint']
|
||||
),
|
||||
'fcm' => new FCM($credentials['serverKey']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function email(Document $provider): ?EmailAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']),
|
||||
'sendgrid' => new SendGrid($credentials['apiKey']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function buildEmailMessage(Document $message, Document $provider): Email
|
||||
{
|
||||
$from = $provider['options']['from'];
|
||||
$to = $message['to'];
|
||||
$subject = $message['data']['subject'];
|
||||
$content = $message['data']['content'];
|
||||
$html = $message['data']['html'];
|
||||
|
||||
return new Email($to, $subject, $content, $from, null, $html);
|
||||
}
|
||||
|
||||
private function buildSMSMessage(Document $message, Document $provider): SMS
|
||||
{
|
||||
$to = $message['to'];
|
||||
$content = $message['data']['content'];
|
||||
$from = $provider['options']['from'];
|
||||
|
||||
return new SMS($to, $content, $from);
|
||||
}
|
||||
|
||||
private function buildPushMessage(Document $message): Push
|
||||
{
|
||||
$to = $message['to'];
|
||||
$title = $message['data']['title'];
|
||||
$body = $message['data']['body'];
|
||||
$data = $message['data']['data'];
|
||||
$action = $message['data']['action'];
|
||||
$sound = $message['data']['sound'];
|
||||
$icon = $message['data']['icon'];
|
||||
$color = $message['data']['color'];
|
||||
$tag = $message['data']['tag'];
|
||||
$badge = $message['data']['badge'];
|
||||
|
||||
return new Push($to, $title, $body, $data, $action, $sound, $icon, $color, $tag, $badge);
|
||||
}
|
||||
}
|
||||
|
|
28
src/Appwrite/Utopia/Database/Validator/Queries/Messages.php
Normal file
28
src/Appwrite/Utopia/Database/Validator/Queries/Messages.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Messages extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'topics',
|
||||
'users',
|
||||
'targets',
|
||||
'providerId',
|
||||
'deliveredAt',
|
||||
'deliveredTo',
|
||||
'deliveryErrors',
|
||||
'status',
|
||||
'description',
|
||||
'data'
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('messages', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
22
src/Appwrite/Utopia/Database/Validator/Queries/Providers.php
Normal file
22
src/Appwrite/Utopia/Database/Validator/Queries/Providers.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Providers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'provider',
|
||||
'type',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('providers', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Subscribers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'targetId',
|
||||
'topicId',
|
||||
'userId',
|
||||
'providerType'
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('subscribers', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
21
src/Appwrite/Utopia/Database/Validator/Queries/Targets.php
Normal file
21
src/Appwrite/Utopia/Database/Validator/Queries/Targets.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Targets extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'userId',
|
||||
'providerId',
|
||||
'identifier',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('targets', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
20
src/Appwrite/Utopia/Database/Validator/Queries/Topics.php
Normal file
20
src/Appwrite/Utopia/Database/Validator/Queries/Topics.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Topics extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('topics', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
|
|||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\AttributeRelationship;
|
||||
use Appwrite\Utopia\Response\Model\AuthProvider;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Branch;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
|
@ -80,8 +81,12 @@ use Appwrite\Utopia\Response\Model\HealthVersion;
|
|||
use Appwrite\Utopia\Response\Model\Installation;
|
||||
use Appwrite\Utopia\Response\Model\LocaleCode;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\Message;
|
||||
use Appwrite\Utopia\Response\Model\Subscriber;
|
||||
use Appwrite\Utopia\Response\Model\Topic;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\Target;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSMS;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
|
@ -191,6 +196,18 @@ class Response extends SwooleResponse
|
|||
public const MODEL_PHONE = 'phone';
|
||||
public const MODEL_PHONE_LIST = 'phoneList';
|
||||
|
||||
// Messaging
|
||||
public const MODEL_PROVIDER = 'provider';
|
||||
public const MODEL_PROVIDER_LIST = 'providerList';
|
||||
public const MODEL_MESSAGE = 'message';
|
||||
public const MODEL_MESSAGE_LIST = 'messageList';
|
||||
public const MODEL_TOPIC = 'topic';
|
||||
public const MODEL_TOPIC_LIST = 'topicList';
|
||||
public const MODEL_SUBSCRIBER = 'subscriber';
|
||||
public const MODEL_SUBSCRIBER_LIST = 'subscriberList';
|
||||
public const MODEL_TARGET = 'target';
|
||||
public const MODEL_TARGET_LIST = 'targetList';
|
||||
|
||||
// Teams
|
||||
public const MODEL_TEAM = 'team';
|
||||
public const MODEL_TEAM_LIST = 'teamList';
|
||||
|
@ -238,8 +255,8 @@ class Response extends SwooleResponse
|
|||
public const MODEL_WEBHOOK_LIST = 'webhookList';
|
||||
public const MODEL_KEY = 'key';
|
||||
public const MODEL_KEY_LIST = 'keyList';
|
||||
public const MODEL_PROVIDER = 'provider';
|
||||
public const MODEL_PROVIDER_LIST = 'providerList';
|
||||
public const MODEL_AUTH_PROVIDER = 'authProvider';
|
||||
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
|
||||
public const MODEL_PLATFORM = 'platform';
|
||||
public const MODEL_PLATFORM_LIST = 'platformList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
|
@ -316,7 +333,7 @@ class Response extends SwooleResponse
|
|||
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
|
||||
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
|
||||
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
|
||||
->setModel(new BaseList('Providers List', self::MODEL_PROVIDER_LIST, 'platforms', self::MODEL_PROVIDER, true, false))
|
||||
->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false))
|
||||
->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false))
|
||||
->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY))
|
||||
->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT))
|
||||
|
@ -328,6 +345,11 @@ class Response extends SwooleResponse
|
|||
->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS))
|
||||
->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE))
|
||||
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
|
||||
->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER))
|
||||
->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE))
|
||||
->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC))
|
||||
->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER))
|
||||
->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET))
|
||||
->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION))
|
||||
->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT))
|
||||
// Entities
|
||||
|
@ -380,7 +402,7 @@ class Response extends SwooleResponse
|
|||
->setModel(new Project())
|
||||
->setModel(new Webhook())
|
||||
->setModel(new Key())
|
||||
->setModel(new Provider())
|
||||
->setModel(new AuthProvider())
|
||||
->setModel(new Platform())
|
||||
->setModel(new Variable())
|
||||
->setModel(new Country())
|
||||
|
@ -408,6 +430,11 @@ class Response extends SwooleResponse
|
|||
->setModel(new TemplateSMS())
|
||||
->setModel(new TemplateEmail())
|
||||
->setModel(new ConsoleVariables())
|
||||
->setModel(new Provider())
|
||||
->setModel(new Message())
|
||||
->setModel(new Topic())
|
||||
->setModel(new Subscriber())
|
||||
->setModel(new Target())
|
||||
->setModel(new Migration())
|
||||
->setModel(new MigrationReport())
|
||||
->setModel(new MigrationFirebaseProject())
|
||||
|
|
|
@ -60,8 +60,8 @@ class V13 extends Filter
|
|||
|
||||
protected function parseProject($content)
|
||||
{
|
||||
$content['providers'] = $content['authProviders'];
|
||||
unset($content['authProviders']);
|
||||
$content['providers'] = $content['oAuthProviders'];
|
||||
unset($content['oAuthProviders']);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
|
|
@ -88,9 +88,9 @@ class V16 extends Filter
|
|||
|
||||
protected function parseProject(array $content)
|
||||
{
|
||||
foreach ($content['providers'] ?? [] as $i => $provider) {
|
||||
$content['providers'][$i]['name'] = \ucfirst($provider['key']);
|
||||
unset($content['providers'][$i]['key']);
|
||||
foreach ($content['oAuthProviders'] ?? [] as $i => $provider) {
|
||||
$content['oAuthProviders'][$i]['name'] = \ucfirst($provider['key']);
|
||||
unset($content['oAuthProviders'][$i]['key']);
|
||||
}
|
||||
|
||||
$content['domains'] = [];
|
||||
|
|
69
src/Appwrite/Utopia/Response/Model/AuthProvider.php
Normal file
69
src/Appwrite/Utopia/Response/Model/AuthProvider.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class AuthProvider extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('key', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auth Provider.',
|
||||
'default' => '',
|
||||
'example' => 'github',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auth Provider name.',
|
||||
'default' => '',
|
||||
'example' => 'GitHub',
|
||||
])
|
||||
->addRule('appId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('secret', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
|
||||
'default' => '',
|
||||
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
|
||||
])
|
||||
->addRule('enabled', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Auth Provider is active and can be used to create session.',
|
||||
'example' => '',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'AuthProvider';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_AUTH_PROVIDER;
|
||||
}
|
||||
}
|
131
src/Appwrite/Utopia/Response/Model/Message.php
Normal file
131
src/Appwrite/Utopia/Response/Model/Message.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document as DatabaseDocument;
|
||||
|
||||
class Message extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->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('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message provider type.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
])
|
||||
->addRule('topics', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic IDs set as recipients.',
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'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('scheduledAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The scheduled time for message.',
|
||||
'required' => false,
|
||||
'default' => DateTime::now(),
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('deliveredAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The time when the message was delivered.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('deliveryErrors', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Delivery errors if any.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['Failed to send message to target 5e5ea5c16897e: Credentials not valid.'],
|
||||
])
|
||||
->addRule('deliveredTotal', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of recipients the message was delivered to.',
|
||||
'default' => 0,
|
||||
'example' => 1,
|
||||
])
|
||||
->addRule('data', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Data of the message.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'subject' => 'Welcome to Appwrite',
|
||||
'content' => 'Hi there, welcome to Appwrite family.',
|
||||
],
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Status of delivery.',
|
||||
'default' => 'processing',
|
||||
'example' => 'Message status can be one of the following: processing, sent, cancelled, failed.',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message description.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => 'Welcome Email.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Message';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_MESSAGE;
|
||||
}
|
||||
}
|
|
@ -138,9 +138,9 @@ class Project extends Model
|
|||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('providers', [
|
||||
'type' => Response::MODEL_PROVIDER,
|
||||
'description' => 'List of Providers.',
|
||||
->addRule('oAuthProviders', [
|
||||
'type' => Response::MODEL_AUTH_PROVIDER,
|
||||
'description' => 'List of Auth Providers.',
|
||||
'default' => [],
|
||||
'example' => [new \stdClass()],
|
||||
'array' => true,
|
||||
|
@ -328,9 +328,9 @@ class Project extends Model
|
|||
$document->setAttribute('auth' . ucfirst($key), $value);
|
||||
}
|
||||
|
||||
// Providers
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providerValues = $document->getAttribute('authProviders', []);
|
||||
// OAuth Providers
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$providerValues = $document->getAttribute('oAuthProviders', []);
|
||||
$projectProviders = [];
|
||||
|
||||
foreach ($providers as $key => $provider) {
|
||||
|
@ -348,7 +348,7 @@ class Project extends Model
|
|||
]);
|
||||
}
|
||||
|
||||
$document->setAttribute("providers", $projectProviders);
|
||||
$document->setAttribute('oAuthProviders', $projectProviders);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
|
|
@ -7,44 +7,68 @@ use Appwrite\Utopia\Response\Model;
|
|||
|
||||
class Provider extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('key', [
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider.',
|
||||
'description' => 'Provider ID.',
|
||||
'default' => '',
|
||||
'example' => 'github',
|
||||
'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' => 'Provider name.',
|
||||
'description' => 'The name for the provider instance.',
|
||||
'default' => '',
|
||||
'example' => 'GitHub',
|
||||
'example' => 'Mailgun',
|
||||
])
|
||||
->addRule('appId', [
|
||||
->addRule('provider', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application ID.',
|
||||
'description' => 'The name of the provider service.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('secret', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
|
||||
'default' => '',
|
||||
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
|
||||
'example' => 'mailgun',
|
||||
])
|
||||
->addRule('enabled', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Provider is active and can be used to create session.',
|
||||
'example' => '',
|
||||
'description' => 'Is provider enabled?',
|
||||
'default' => true,
|
||||
'example' => true,
|
||||
])
|
||||
;
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Type of provider.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_SMS,
|
||||
])
|
||||
->addRule('credentials', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Provider credentials.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'key' => '123456789'
|
||||
],
|
||||
])
|
||||
->addRule('options', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Provider options.',
|
||||
'default' => [],
|
||||
'required' => false,
|
||||
'example' => [
|
||||
'from' => 'sender-email@mydomain'
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
97
src/Appwrite/Utopia/Response/Model/Subscriber.php
Normal file
97
src/Appwrite/Utopia/Response/Model/Subscriber.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Subscriber extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Subscriber ID.',
|
||||
'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.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('target', [
|
||||
'type' => Response::MODEL_TARGET,
|
||||
'description' => 'Target.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'$id' => '259125845563242502',
|
||||
'$createdAt' => self::TYPE_DATETIME_EXAMPLE,
|
||||
'$updatedAt' => self::TYPE_DATETIME_EXAMPLE,
|
||||
'providerType' => 'email',
|
||||
'providerId' => '259125845563242502',
|
||||
'name' => 'ageon-app-email',
|
||||
'identifier' => 'random-mail@email.org',
|
||||
'userId' => '5e5ea5c16897e',
|
||||
],
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('userName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User Name.',
|
||||
'default' => '',
|
||||
'example' => 'Aegon Targaryen',
|
||||
])
|
||||
->addRule('topicId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Subscriber';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_SUBSCRIBER;
|
||||
}
|
||||
}
|
83
src/Appwrite/Utopia/Response/Model/Target.php
Normal file
83
src/Appwrite/Utopia/Response/Model/Target.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Target extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target ID.',
|
||||
'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('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target Name.',
|
||||
'default' => '',
|
||||
'example' => 'Aegon apple token',
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider ID.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
])
|
||||
->addRule('identifier', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target identifier.',
|
||||
'default' => '',
|
||||
'example' => 'token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Target';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TARGET;
|
||||
}
|
||||
}
|
71
src/Appwrite/Utopia/Response/Model/Topic.php
Normal file
71
src/Appwrite/Utopia/Response/Model/Topic.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Topic extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'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.',
|
||||
'default' => '',
|
||||
'example' => 'events',
|
||||
])
|
||||
->addRule('total', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Total count of subscribers subscribed to topic.',
|
||||
'default' => 0,
|
||||
'example' => 100,
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Description of the topic.',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'example' => 'All events related messages will be sent to this topic.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Topic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TOPIC;
|
||||
}
|
||||
}
|
|
@ -120,6 +120,13 @@ class User extends Model
|
|||
'default' => new \stdClass(),
|
||||
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
|
||||
])
|
||||
->addRule('targets', [
|
||||
'type' => Response::MODEL_TARGET,
|
||||
'description' => 'A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider.',
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'example' => [],
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCCESS / 60 / 60 . ' hours.',
|
||||
|
|
|
@ -82,7 +82,17 @@ trait ProjectCustom
|
|||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write'
|
||||
'rules.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
|
@ -94,6 +94,36 @@ trait AccountBase
|
|||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$shortPassword = 'short';
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'shortpass@appwrite.io',
|
||||
'password' => $shortPassword
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$longPassword = '';
|
||||
for ($i = 0; $i < 257; $i++) { // 256 is the limit
|
||||
$longPassword .= 'p';
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'longpass@appwrite.io',
|
||||
'password' => $longPassword,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'email' => $email,
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
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;
|
||||
|
||||
|
@ -764,6 +764,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
$messageId = $response['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
|
|
@ -6,7 +6,9 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
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,6 +125,7 @@ class AccountTest extends Scope
|
|||
public function testCreatePhoneVerification(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
|
|
|
@ -109,6 +109,11 @@ trait Base
|
|||
public static string $DELETE_USER_SESSIONS = 'delete_user_sessions';
|
||||
public static string $DELETE_USER_SESSION = 'delete_user_session';
|
||||
public static string $DELETE_USER = 'delete_user';
|
||||
public static string $CREATE_USER_TARGET = 'create_user_target';
|
||||
public static string $LIST_USER_TARGETS = 'list_user_targets';
|
||||
public static string $GET_USER_TARGET = 'get_user_target';
|
||||
public static string $UPDATE_USER_TARGET = 'update_user_target';
|
||||
public static string $DELETE_USER_TARGET = 'delete_user_target';
|
||||
|
||||
// Teams
|
||||
public static string $GET_TEAM = 'get_team';
|
||||
|
@ -199,6 +204,53 @@ trait Base
|
|||
public static string $GET_QRCODE = 'get_qrcode';
|
||||
public static string $GET_USER_INITIALS = 'get_user_initials';
|
||||
|
||||
// Providers
|
||||
public static string $CREATE_MAILGUN_PROVIDER = 'create_mailgun_provider';
|
||||
public static string $CREATE_SENDGRID_PROVIDER = 'create_sendgrid_provider';
|
||||
public static string $CREATE_TWILIO_PROVIDER = 'create_twilio_provider';
|
||||
public static string $CREATE_TELESIGN_PROVIDER = 'create_telesign_provider';
|
||||
public static string $CREATE_TEXTMAGIC_PROVIDER = 'create_textmagic_provider';
|
||||
public static string $CREATE_MSG91_PROVIDER = 'create_msg91_provider';
|
||||
public static string $CREATE_VONAGE_PROVIDER = 'create_vonage_provider';
|
||||
public static string $CREATE_FCM_PROVIDER = 'create_fcm_provider';
|
||||
public static string $CREATE_APNS_PROVIDER = 'create_apns_provider';
|
||||
public static string $LIST_PROVIDERS = 'list_providers';
|
||||
public static string $GET_PROVIDER = 'get_provider';
|
||||
public static string $UPDATE_MAILGUN_PROVIDER = 'update_mailgun_provider';
|
||||
public static string $UPDATE_SENDGRID_PROVIDER = 'update_sendgrid_provider';
|
||||
public static string $UPDATE_TWILIO_PROVIDER = 'update_twilio_provider';
|
||||
public static string $UPDATE_TELESIGN_PROVIDER = 'update_telesign_provider';
|
||||
public static string $UPDATE_TEXTMAGIC_PROVIDER = 'update_textmagic_provider';
|
||||
public static string $UPDATE_MSG91_PROVIDER = 'update_msg91_provider';
|
||||
public static string $UPDATE_VONAGE_PROVIDER = 'update_vonage_provider';
|
||||
public static string $UPDATE_FCM_PROVIDER = 'update_fcm_provider';
|
||||
public static string $UPDATE_APNS_PROVIDER = 'update_apns_provider';
|
||||
public static string $DELETE_PROVIDER = 'delete_provider';
|
||||
|
||||
// Topics
|
||||
public static string $CREATE_TOPIC = 'create_topic';
|
||||
public static string $LIST_TOPICS = 'list_topics';
|
||||
public static string $GET_TOPIC = 'get_topic';
|
||||
public static string $UPDATE_TOPIC = 'update_topic';
|
||||
public static string $DELETE_TOPIC = 'delete_topic';
|
||||
|
||||
// Subscriptions
|
||||
public static string $CREATE_SUBSCRIBER = 'create_subscriber';
|
||||
public static string $LIST_SUBSCRIBERS = 'list_subscribers';
|
||||
public static string $GET_SUBSCRIBER = 'get_subscriber';
|
||||
public static string $DELETE_SUBSCRIBER = 'delete_subscriber';
|
||||
|
||||
// Messages
|
||||
public static string $CREATE_EMAIL = 'create_email';
|
||||
public static string $CREATE_SMS = 'create_sms';
|
||||
public static string $CREATE_PUSH_NOTIFICATION = 'create_push_notification';
|
||||
public static string $LIST_MESSAGES = 'list_messages';
|
||||
public static string $GET_MESSAGE = 'get_message';
|
||||
|
||||
public static string $UPDATE_EMAIL = 'update_email';
|
||||
public static string $UPDATE_SMS = 'update_sms';
|
||||
public static string $UPDATE_PUSH_NOTIFICATION = 'update_push_notification';
|
||||
|
||||
// Complex queries
|
||||
public static string $COMPLEX_QUERY = 'complex_query';
|
||||
|
||||
|
@ -881,6 +933,55 @@ trait Base
|
|||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_USER_TARGET:
|
||||
return 'mutation createUserTarget($userId: String!, $targetId: String!, $providerType: String!, $identifier: String! $providerId: String){
|
||||
usersCreateTarget(userId: $userId, targetId: $targetId, providerType: $providerType, identifier: $identifier, providerId: $providerId) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$LIST_USER_TARGETS:
|
||||
return 'query listUserTargets($userId: String!) {
|
||||
usersListTargets(userId: $userId) {
|
||||
total
|
||||
targets {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_USER_TARGET:
|
||||
return 'query getUserTarget($userId: String!, $targetId: String!) {
|
||||
usersGetTarget(userId: $userId, targetId: $targetId) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_USER_TARGET:
|
||||
return 'mutation updateUserTarget($userId: String!, $targetId: String!, $providerId: String, $identifier: String){
|
||||
usersUpdateTarget(userId: $userId, targetId: $targetId, providerId: $providerId, identifier: $identifier) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_USER_TARGET:
|
||||
return 'mutation deleteUserTarget($userId: String!, $targetId: String!){
|
||||
usersDeleteTarget(userId: $userId, targetId: $targetId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$GET_LOCALE:
|
||||
return 'query getLocale {
|
||||
localeGet {
|
||||
|
@ -1688,6 +1789,439 @@ trait Base
|
|||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MAILGUN_PROVIDER:
|
||||
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $from: String!, $isEuRegion: Boolean!) {
|
||||
messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, from: $from, isEuRegion: $isEuRegion) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SENDGRID_PROVIDER:
|
||||
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!) {
|
||||
messagingCreateSendgridProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TWILIO_PROVIDER:
|
||||
return 'mutation createTwilioProvider($providerId: String!, $name: String!, $from: String!, $accountSid: String!, $authToken: String!) {
|
||||
messagingCreateTwilioProvider(providerId: $providerId, name: $name, from: $from, accountSid: $accountSid, authToken: $authToken) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TELESIGN_PROVIDER:
|
||||
return 'mutation createTelesignProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $password: String!) {
|
||||
messagingCreateTelesignProvider(providerId: $providerId, name: $name, from: $from, username: $username, password: $password) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TEXTMAGIC_PROVIDER:
|
||||
return 'mutation createTextmagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) {
|
||||
messagingCreateTextmagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MSG91_PROVIDER:
|
||||
return 'mutation createMsg91Provider($providerId: String!, $name: String!, $from: String!, $senderId: String!, $authKey: String!, $enabled: Boolean) {
|
||||
messagingCreateMsg91Provider(providerId: $providerId, name: $name, from: $from, senderId: $senderId, authKey: $authKey, enabled: $enabled) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_VONAGE_PROVIDER:
|
||||
return 'mutation createVonageProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!, $apiSecret: String!) {
|
||||
messagingCreateVonageProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey, apiSecret: $apiSecret) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_FCM_PROVIDER:
|
||||
return 'mutation createFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
|
||||
messagingCreateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_APNS_PROVIDER:
|
||||
return 'mutation createApnsProvider($providerId: String!, $name: String!, $authKey: String!, $authKeyId: String!, $teamId: String!, $bundleId: String!, $endpoint: String!) {
|
||||
messagingCreateApnsProvider(providerId: $providerId, name: $name, authKey: $authKey, authKeyId: $authKeyId, teamId: $teamId, bundleId: $bundleId, endpoint: $endpoint) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$LIST_PROVIDERS:
|
||||
return 'query listProviders {
|
||||
messagingListProviders {
|
||||
total
|
||||
providers {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_PROVIDER:
|
||||
return 'query getProvider($providerId: String!) {
|
||||
messagingGetProvider(providerId: $providerId) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_MAILGUN_PROVIDER:
|
||||
return 'mutation updateMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $isEuRegion: Boolean, $enabled: Boolean) {
|
||||
messagingUpdateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, isEuRegion: $isEuRegion, enabled: $enabled) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_SENDGRID_PROVIDER:
|
||||
return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!) {
|
||||
messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TWILIO_PROVIDER:
|
||||
return 'mutation updateTwilioProvider($providerId: String!, $name: String!, $accountSid: String!, $authToken: String!) {
|
||||
messagingUpdateTwilioProvider(providerId: $providerId, name: $name, accountSid: $accountSid, authToken: $authToken) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TELESIGN_PROVIDER:
|
||||
return 'mutation updateTelesignProvider($providerId: String!, $name: String!, $username: String!, $password: String!) {
|
||||
messagingUpdateTelesignProvider(providerId: $providerId, name: $name, username: $username, password: $password) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TEXTMAGIC_PROVIDER:
|
||||
return 'mutation updateTextmagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) {
|
||||
messagingUpdateTextmagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_MSG91_PROVIDER:
|
||||
return 'mutation updateMsg91Provider($providerId: String!, $name: String!, $senderId: String!, $authKey: String!) {
|
||||
messagingUpdateMsg91Provider(providerId: $providerId, name: $name, senderId: $senderId, authKey: $authKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_VONAGE_PROVIDER:
|
||||
return 'mutation updateVonageProvider($providerId: String!, $name: String!, $apiKey: String!, $apiSecret: String!) {
|
||||
messagingUpdateVonageProvider(providerId: $providerId, name: $name, apiKey: $apiKey, apiSecret: $apiSecret) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_FCM_PROVIDER:
|
||||
return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
|
||||
messagingUpdateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_APNS_PROVIDER:
|
||||
return 'mutation updateApnsProvider($providerId: String!, $name: String!, $authKey: String!, $authKeyId: String!, $teamId: String!, $bundleId: String!, $endpoint: String!) {
|
||||
messagingUpdateApnsProvider(providerId: $providerId, name: $name, authKey: $authKey, authKeyId: $authKeyId, teamId: $teamId, bundleId: $bundleId, endpoint: $endpoint) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_PROVIDER:
|
||||
return 'mutation deleteProvider($providerId: String!) {
|
||||
messagingDeleteProvider(providerId: $providerId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TOPIC:
|
||||
return 'mutation createTopic($topicId: String!, $name: String!, $description: String!) {
|
||||
messagingCreateTopic(topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_TOPICS:
|
||||
return 'query listTopics {
|
||||
messagingListTopics {
|
||||
total
|
||||
topics {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_TOPIC:
|
||||
return 'query getTopic($topicId: String!) {
|
||||
messagingGetTopic(topicId: $topicId) {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TOPIC:
|
||||
return 'mutation updateTopic($topicId: String!, $name: String!, $description: String!) {
|
||||
messagingUpdateTopic(topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_TOPIC:
|
||||
return 'mutation deleteTopic($topicId: String!) {
|
||||
messagingDeleteTopic(topicId: $topicId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SUBSCRIBER:
|
||||
return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) {
|
||||
messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$LIST_SUBSCRIBERS:
|
||||
return 'query listSubscribers($topicId: String!) {
|
||||
messagingListSubscribers(topicId: $topicId) {
|
||||
total
|
||||
subscribers {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_SUBSCRIBER:
|
||||
return 'query getSubscriber($topicId: String!, $subscriberId: String!) {
|
||||
messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_SUBSCRIBER:
|
||||
return 'mutation deleteSubscriber($topicId: String!, $subscriberId: String!) {
|
||||
messagingDeleteSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_EMAIL:
|
||||
return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
|
||||
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SMS:
|
||||
return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_PUSH_NOTIFICATION:
|
||||
return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_MESSAGES:
|
||||
return 'query listMessages {
|
||||
messagingListMessages {
|
||||
total
|
||||
messages {
|
||||
_id
|
||||
providerType
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_MESSAGE:
|
||||
return 'query getMessage($messageId: String!) {
|
||||
messagingGetMessage(messageId: $messageId) {
|
||||
_id
|
||||
providerType
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_EMAIL:
|
||||
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
|
||||
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_SMS:
|
||||
return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_PUSH_NOTIFICATION:
|
||||
return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$COMPLEX_QUERY:
|
||||
return 'mutation complex($databaseId: String!, $databaseName: String!, $collectionId: String!, $collectionName: String!, $documentSecurity: Boolean!, $collectionPermissions: [String!]!) {
|
||||
databasesCreate(databaseId: $databaseId, name: $databaseName) {
|
||||
|
@ -1940,7 +2474,7 @@ trait Base
|
|||
protected string $stdout = '';
|
||||
protected string $stderr = '';
|
||||
|
||||
protected function packageCode($folder)
|
||||
protected function packageCode($folder): void
|
||||
{
|
||||
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
|
||||
}
|
||||
|
|
1153
tests/e2e/Services/GraphQL/MessagingTest.php
Normal file
1153
tests/e2e/Services/GraphQL/MessagingTest.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -45,6 +45,56 @@ class UsersTest extends Scope
|
|||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUser
|
||||
*/
|
||||
public function testCreateUserTarget(array $user)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Mailgun1',
|
||||
'apiKey' => 'api-key',
|
||||
'domain' => 'domain',
|
||||
'from' => 'from@domain.com',
|
||||
'isEuRegion' => false,
|
||||
],
|
||||
];
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
$providerId = $provider['body']['data']['messagingCreateMailgunProvider']['_id'];
|
||||
|
||||
$this->assertEquals(200, $provider['headers']['status-code']);
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'targetId' => ID::unique(),
|
||||
'userId' => $user['_id'],
|
||||
'providerType' => 'email',
|
||||
'providerId' => $providerId,
|
||||
'identifier' => 'random-email@mail.org',
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('random-email@mail.org', $target['body']['data']['usersCreateTarget']['identifier']);
|
||||
|
||||
return $target['body']['data']['usersCreateTarget'];
|
||||
}
|
||||
|
||||
public function testGetUsers()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
@ -176,6 +226,54 @@ class UsersTest extends Scope
|
|||
$this->assertIsArray($user['body']['data']['usersListLogs']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testListUserTargets(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_USER_TARGETS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
]
|
||||
];
|
||||
|
||||
$targets = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $targets['headers']['status-code']);
|
||||
$this->assertIsArray($targets['body']['data']['usersListTargets']);
|
||||
$this->assertCount(2, $targets['body']['data']['usersListTargets']['targets']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testGetUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('random-email@mail.org', $target['body']['data']['usersGetTarget']['identifier']);
|
||||
}
|
||||
|
||||
public function testUpdateUserStatus()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
@ -360,6 +458,31 @@ class UsersTest extends Scope
|
|||
$this->assertEquals('{"key":"value"}', $user['body']['data']['usersUpdatePrefs']['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testUpdateUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
'identifier' => 'random-email1@mail.org',
|
||||
],
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('random-email1@mail.org', $target['body']['data']['usersUpdateTarget']['identifier']);
|
||||
}
|
||||
|
||||
public function testDeleteUserSessions()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
@ -407,6 +530,29 @@ class UsersTest extends Scope
|
|||
$this->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testDeleteUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(204, $target['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteUser()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
|
1040
tests/e2e/Services/Messaging/MessagingBase.php
Normal file
1040
tests/e2e/Services/Messaging/MessagingBase.php
Normal file
File diff suppressed because it is too large
Load diff
413
tests/e2e/Services/Messaging/MessagingConsoleClientTest.php
Normal file
413
tests/e2e/Services/Messaging/MessagingConsoleClientTest.php
Normal file
|
@ -0,0 +1,413 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class MessagingConsoleClientTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
/**
|
||||
* @depends testListProviders
|
||||
*/
|
||||
public function testGetProviderLogs(array $providers): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providers[0]['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid/', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Sengrid1',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'sender-email@my-domain.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/sendgrid/' . $provider['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'sendgrid' => [
|
||||
'name' => 'Sendgrid2',
|
||||
]]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testListTopic
|
||||
*/
|
||||
public function testGetTopicLogs(string $topicId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topicId . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'my-app',
|
||||
'description' => 'web app'
|
||||
]);
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/topics/' . $topic['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'description' => 'updated-description'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testSendEmail
|
||||
*/
|
||||
public function testGetMessageLogs(array $email): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$email = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'messageId' => ID::unique(),
|
||||
'status' => 'draft',
|
||||
'topics' => [ID::unique()],
|
||||
'subject' => 'Khali beats Undertaker',
|
||||
'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $email['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'subject' => 'Khali beats John Cena!',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
}
|
14
tests/e2e/Services/Messaging/MessagingCustomClientTest.php
Normal file
14
tests/e2e/Services/Messaging/MessagingCustomClientTest.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
|
||||
class MessagingCustomClientTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
}
|
14
tests/e2e/Services/Messaging/MessagingCustomServerTest.php
Normal file
14
tests/e2e/Services/Messaging/MessagingCustomServerTest.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class MessagingCustomServerTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
|
@ -794,7 +794,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
public function testUpdateProjectOAuth($data): array
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
$providers = require('app/config/providers.php');
|
||||
$providers = require('app/config/oAuthProviders.php');
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
|
@ -825,7 +825,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
foreach ($providers as $key => $provider) {
|
||||
$asserted = false;
|
||||
foreach ($response['body']['providers'] as $responseProvider) {
|
||||
foreach ($response['body']['oAuthProviders'] as $responseProvider) {
|
||||
if ($responseProvider['key'] === $key) {
|
||||
$this->assertEquals('AppId-' . ucfirst($key), $responseProvider['appId']);
|
||||
$this->assertEquals('Secret-' . ucfirst($key), $responseProvider['secret']);
|
||||
|
@ -867,7 +867,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$i = 0;
|
||||
foreach ($providers as $key => $provider) {
|
||||
$asserted = false;
|
||||
foreach ($response['body']['providers'] as $responseProvider) {
|
||||
foreach ($response['body']['oAuthProviders'] as $responseProvider) {
|
||||
if ($responseProvider['key'] === $key) {
|
||||
// On first provider, test enabled=false
|
||||
$this->assertEquals($i !== 0, $responseProvider['enabled']);
|
||||
|
|
|
@ -23,6 +23,7 @@ trait UsersBase
|
|||
'password' => 'password',
|
||||
'name' => 'Cristiano Ronaldo',
|
||||
], false);
|
||||
$this->assertEquals($user['headers']['status-code'], 201);
|
||||
|
||||
// Test empty prefs is object not array
|
||||
$bodyString = $user['body'];
|
||||
|
@ -1223,6 +1224,99 @@ trait UsersBase
|
|||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
public function testCreateUserTarget(array $data): array
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Sengrid1',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'from@domain.com',
|
||||
]);
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'targetId' => ID::unique(),
|
||||
'providerId' => $provider['body']['$id'],
|
||||
'providerType' => 'email',
|
||||
'identifier' => 'random-email@mail.org',
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($provider['body']['$id'], $response['body']['providerId']);
|
||||
$this->assertEquals('random-email@mail.org', $response['body']['identifier']);
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testUpdateUserTarget(array $data): array
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/targets/' . $data['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'identifier' => 'random-email1@mail.org',
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('random-email1@mail.org', $response['body']['identifier']);
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testListUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(2, \count($response['body']['targets']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testGetUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets/' . $data['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($data['$id'], $response['body']['$id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testDeleteUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $data['userId'] . '/targets/' . $data['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
|
|
|
@ -24,5 +24,16 @@ class PasswordDictionaryTest extends TestCase
|
|||
$this->assertEquals($this->object->isValid('123456'), false);
|
||||
$this->assertEquals($this->object->isValid('password'), false);
|
||||
$this->assertEquals($this->object->isValid('myPasswordIsRight'), true);
|
||||
|
||||
$pass = ''; // 256 chars
|
||||
for ($i = 0; $i < 256; $i++) {
|
||||
$pass .= 'p';
|
||||
}
|
||||
|
||||
$this->assertEquals($this->object->isValid($pass), true);
|
||||
|
||||
$pass .= 'p'; // 257 chars
|
||||
|
||||
$this->assertEquals($this->object->isValid($pass), false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,9 +154,9 @@ class V16Test extends TestCase
|
|||
public function projectProvider(): array
|
||||
{
|
||||
return [
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'key' => 'github',
|
||||
'name' => 'GitHub',
|
||||
|
@ -167,7 +167,7 @@ class V16Test extends TestCase
|
|||
],
|
||||
],
|
||||
[
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'name' => 'Github',
|
||||
'appId' => 'client_id',
|
||||
|
|
Loading…
Reference in a new issue