From f07f246f8db2dd4ae3525ed31a1f09de8b879f53 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Fri, 22 Sep 2023 22:53:41 +0530 Subject: [PATCH 01/54] fix conflicts --- app/config/errors.php | 9 +++++++++ app/console | 2 +- app/controllers/api/account.php | 8 ++++++++ src/Appwrite/Extend/Exception.php | 2 ++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/config/errors.php b/app/config/errors.php index 575a4588ba..560741b9d3 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -239,6 +239,15 @@ return [ 'name' => Exception::USER_OAUTH2_PROVIDER_ERROR, 'description' => 'OAuth2 provider returned some error.', 'code' => 424, + Exception::USER_EMAIL_ALREADY_VERIFIED => [ + 'name' => Exception::USER_EMAIL_ALREADY_VERIFIED, + 'description' => 'User email is already verified', + 'code' => 400, + ], + Exception::USER_PHONE_ALREADY_VERIFIED => [ + 'name' => Exception::USER_PHONE_ALREADY_VERIFIED, + 'description' => 'User phone is already verified', + 'code' => 400 ], /** Teams */ diff --git a/app/console b/app/console index 9b4bcb8140..88b6d59051 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 9b4bcb8140484669421685b4ba89fa1c4d331360 +Subproject commit 88b6d59051992ed86183ee83d77bf678d1cb73bf diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 87dcd95f03..575a343391 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2669,6 +2669,10 @@ App::post('/v1/account/verification') if (empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } + + if($user->getAttribute('emailVerification') == true){ + throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); + } $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ -2894,6 +2898,10 @@ App::post('/v1/account/verification/phone') if (empty($user->getAttribute('phone'))) { throw new Exception(Exception::USER_PHONE_NOT_FOUND); } + + if($user->getAttribute('phoneVerification') == true){ + throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED); + } $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 77dc03e310..5f779ff841 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -84,6 +84,8 @@ class Exception extends \Exception public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request'; public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; 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'; /** Teams */ public const TEAM_NOT_FOUND = 'team_not_found'; From 6b4799912069ff6fed0442120a2147f4b01383c9 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Fri, 22 Sep 2023 22:56:07 +0530 Subject: [PATCH 02/54] fix conflicts --- app/config/errors.php | 4 ++-- app/controllers/api/account.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 560741b9d3..192b03e2b0 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -242,12 +242,12 @@ return [ Exception::USER_EMAIL_ALREADY_VERIFIED => [ 'name' => Exception::USER_EMAIL_ALREADY_VERIFIED, 'description' => 'User email is already verified', - 'code' => 400, + 'code' => 409, ], Exception::USER_PHONE_ALREADY_VERIFIED => [ 'name' => Exception::USER_PHONE_ALREADY_VERIFIED, 'description' => 'User phone is already verified', - 'code' => 400 + 'code' => 409 ], /** Teams */ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 575a343391..9b21ef7d0e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2669,8 +2669,8 @@ App::post('/v1/account/verification') if (empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } - - if($user->getAttribute('emailVerification') == true){ + + if ($user->getAttribute('emailVerification')) { throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } @@ -2898,8 +2898,8 @@ App::post('/v1/account/verification/phone') if (empty($user->getAttribute('phone'))) { throw new Exception(Exception::USER_PHONE_NOT_FOUND); } - - if($user->getAttribute('phoneVerification') == true){ + + if ($user->getAttribute('phoneVerification')) { throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED); } From 56a3b3df9927b8e5fa9823023fe22c76c7c6dee1 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Sun, 7 May 2023 03:16:34 +0000 Subject: [PATCH 03/54] Add test cases --- tests/e2e/Services/Account/AccountBase.php | 53 +++++++++++++++++++ .../Account/AccountCustomClientTest.php | 24 +++++++++ 2 files changed, 77 insertions(+) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index e64cf2c4ca..57d6d8de83 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -891,6 +891,7 @@ trait AccountBase return $data; } + /** * @depends testCreateAccountVerification */ @@ -945,6 +946,58 @@ trait AccountBase return $data; } + /** + * @depends testUpdateAccountVerification + */ + public function testCreateAccountVerificationForVerifiedEmail($data): array + { + $email = $data['email'] ?? ''; + $name = $data['name'] ?? ''; + $session = $data['session'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $this->assertEquals(409, $response['headers']['status-code']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]), [ + 'url' => 'localhost/verification', + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]), [ + 'url' => 'http://remotehost/verification', + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + return $data; + } + /** * @depends testUpdateAccountVerification */ diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 1441ab7f98..ee8978d73a 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1068,4 +1068,28 @@ class AccountCustomClientTest extends Scope return $data; } + + /** + * @depends testPhoneVerification + */ + #[Retry(count: 1)] + public function testPhoneVerificationForVerifiedPhone(array $data): array + { + $session = $data['session'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + + ])); + + $this->assertEquals(409, $response['headers']['status-code']); + + return \array_merge($data); + } } From 5771ad35ed4bf9c9160b2df5e5a75b9d56ae13c7 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Wed, 21 Jun 2023 15:55:39 +0000 Subject: [PATCH 04/54] fix test cases --- tests/e2e/Services/Account/AccountBase.php | 25 ------------------- .../Account/AccountCustomClientTest.php | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 57d6d8de83..e2f56d54a4 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -970,31 +970,6 @@ trait AccountBase $this->assertEquals(409, $response['headers']['status-code']); - /** - * Test for FAILURE - */ - $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), [ - 'url' => 'localhost/verification', - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), [ - 'url' => 'http://remotehost/verification', - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - return $data; } diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index ee8978d73a..1719d4e049 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1090,6 +1090,6 @@ class AccountCustomClientTest extends Scope $this->assertEquals(409, $response['headers']['status-code']); - return \array_merge($data); + return $data; } } From b420c570bc2e76753eaaf540d345b86eafd3c7ef Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Sat, 14 Oct 2023 11:51:27 +0530 Subject: [PATCH 05/54] Reverted to last commit before merging main --- app/console | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/console b/app/console index 88b6d59051..9b4bcb8140 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 88b6d59051992ed86183ee83d77bf678d1cb73bf +Subproject commit 9b4bcb8140484669421685b4ba89fa1c4d331360 From 1489f77499727711195ba0404122efbaafa61b90 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Sat, 14 Oct 2023 12:45:29 +0530 Subject: [PATCH 06/54] fix changes suggested by steven and also fixed some bugs came after merging with 1.4.x --- app/config/errors.php | 1 + tests/e2e/Services/Account/AccountBase.php | 2 - .../Account/AccountCustomClientTest.php | 1 - .../Webhooks/WebhooksCustomClientTest.php | 228 +++++++++--------- 4 files changed, 115 insertions(+), 117 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 192b03e2b0..e287b846b1 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -239,6 +239,7 @@ return [ 'name' => Exception::USER_OAUTH2_PROVIDER_ERROR, 'description' => 'OAuth2 provider returned some error.', 'code' => 424, + ], Exception::USER_EMAIL_ALREADY_VERIFIED => [ 'name' => Exception::USER_EMAIL_ALREADY_VERIFIED, 'description' => 'User email is already verified', diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index e2f56d54a4..953b89ced7 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -891,7 +891,6 @@ trait AccountBase return $data; } - /** * @depends testCreateAccountVerification */ @@ -963,7 +962,6 @@ trait AccountBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), [ 'url' => 'http://localhost/verification', ]); diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 1719d4e049..04322292d8 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1085,7 +1085,6 @@ class AccountCustomClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ])); $this->assertEquals(409, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 67c2dec36b..21c270d199 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -636,6 +636,120 @@ class WebhooksCustomClientTest extends Scope return $data; } + /** + * @depends testUpdateAccountPrefs + */ + public function testCreateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $verificationId = $verification['body']['$id']; + + $this->assertEquals(201, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + $signatureKey = $this->getProject()['signatureKey']; + $payload = json_encode($webhook['data']); + $url = $webhook['url']; + $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire'])); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testCreateAccountVerification + */ + public function testUpdateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $secret = $data['secret'] ?? ''; + + $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]), [ + 'userId' => $id, + 'secret' => $secret, + ]); + + $verificationId = $verification['body']['$id']; + + $this->assertEquals(200, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + $signatureKey = $this->getProject()['signatureKey']; + $payload = json_encode($webhook['data']); + $url = $webhook['url']; + $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire'])); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + /** * @depends testUpdateAccountPrefs */ @@ -751,120 +865,6 @@ class WebhooksCustomClientTest extends Scope return $data; } - /** - * @depends testUpdateAccountPrefs - */ - public function testCreateAccountVerification($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - - $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), [ - 'url' => 'http://localhost/verification', - ]); - - $verificationId = $verification['body']['$id']; - - $this->assertEquals(201, $verification['headers']['status-code']); - $this->assertIsArray($verification['body']); - - $webhook = $this->getLastRequest(); - $signatureKey = $this->getProject()['signatureKey']; - $payload = json_encode($webhook['data']); - $url = $webhook['url']; - $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('users.*.verification.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.*.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['userId']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire'])); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - - /** - * @depends testCreateAccountVerification - */ - public function testUpdateAccountVerification($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - $secret = $data['secret'] ?? ''; - - $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), [ - 'userId' => $id, - 'secret' => $secret, - ]); - - $verificationId = $verification['body']['$id']; - - $this->assertEquals(200, $verification['headers']['status-code']); - $this->assertIsArray($verification['body']); - - $webhook = $this->getLastRequest(); - $signatureKey = $this->getProject()['signatureKey']; - $payload = json_encode($webhook['data']); - $url = $webhook['url']; - $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('users.*.verification.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.*.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['userId']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire'])); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - /** * @depends testCreateTeamMembership */ From 68486d4795ff3a620fb5bdbb8460b25b7677f3c7 Mon Sep 17 00:00:00 2001 From: KRISH SONI <67964054+krishvsoni@users.noreply.github.com> Date: Sun, 29 Oct 2023 23:17:27 +0530 Subject: [PATCH 07/54] Update README-CN.md --- README-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-CN.md b/README-CN.md index 1df799f7ef..18915b283c 100644 --- a/README-CN.md +++ b/README-CN.md @@ -55,7 +55,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读 ## 安装 -Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。 +Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 [Kubernetes](https://kubernetes.io/docs/home/)、[Docker Swarm](https://docs.docker.com/engine/swarm/) 或 [Rancher](https://rancher.com/docs/))上运行 Appwrite。 启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html): From 0ecdeed3298e50590438f0e8a96dd14c38096020 Mon Sep 17 00:00:00 2001 From: Yatharth Verma Date: Sat, 4 Nov 2023 13:01:05 +0530 Subject: [PATCH 08/54] Checkout to latest tag of console submodule --- app/console | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/console b/app/console index 9b4bcb8140..9810ce8581 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 9b4bcb8140484669421685b4ba89fa1c4d331360 +Subproject commit 9810ce85812ca26c95b7d35196848c92e8ba813d From 0f3c43897621e892e1b1dceed6378cf385112803 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 8 Nov 2023 12:05:51 +0200 Subject: [PATCH 09/54] added inf metric --- .../Platform/Tasks/DeleteOrphanedProjects.php | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 2824f4e286..aeaab4b248 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -13,6 +13,8 @@ use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Pools\Group; use Utopia\Registry\Registry; +use Utopia\Validator\Boolean; +use Utopia\Validator\Hostname; class DeleteOrphanedProjects extends Action { @@ -26,25 +28,23 @@ class DeleteOrphanedProjects extends Action $this ->desc('Get stats for projects') + ->param('commit', false, new boolean(true), 'Commit project deletion', true) ->inject('pools') ->inject('cache') ->inject('dbForConsole') ->inject('register') - ->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) { - $this->action($pools, $cache, $dbForConsole, $register); + ->callback(function (bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register) { + $this->action($commit, $pools, $cache, $dbForConsole, $register); }); } - public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void + public function action(bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void { Console::title('Delete orphaned projects V1'); Console::success(APP_NAME . ' Delete orphaned projects started'); - /** @var array $collections */ - $collectionsConfig = Config::getParam('collections', [])['projects'] ?? []; - /* Initialise new Utopia app */ $app = new App('UTC'); $console = $app->getResource('console'); @@ -54,7 +54,7 @@ class DeleteOrphanedProjects extends Action $totalProjects = $dbForConsole->count('projects'); Console::success("Found a total of: {$totalProjects} projects"); - $orphans = 0; + $orphans = 1; $count = 0; $limit = 30; $sum = 30; @@ -80,17 +80,22 @@ class DeleteOrphanedProjects extends Action $dbForProject->setDefaultDatabase('appwrite'); $dbForProject->setNamespace('_' . $project->getInternalId()); $collectionsCreated = $dbForProject->count(Database::METADATA); - $message = ' (' . $collectionsCreated . ') collections where found on project (' . $project->getId() . '))'; - if ($collectionsCreated < (count($collectionsConfig) + 2)) { - Console::error($message); + if ($collectionsCreated === 0) { + if ($commit === true) { + Console::info('(' . $orphans . ') deleting project (' . $project->getId() . ')'); + $this->deleteProject($dbForConsole, $project->getId()); + } else { + Console::log('(' . $orphans . ') project (' . $project->getId() . ')'); + } $orphans++; - } else { - Console::log($message); } } catch (\Throwable $th) { - //$dbForConsole->deleteDocument('projects', $project->getId()); - //Console::success('Deleting project (' . $project->getId() . ')'); - Console::error(' (0) collections where found for project (' . $project->getId() . ')'); + if ($commit === true) { + Console::info('(' . $orphans . ') deleting project (' . $project->getId() . ')'); + $this->deleteProject($dbForConsole, $project->getId()); + } else { + Console::log('(' . $orphans . ') project (' . $project->getId() . ')'); + } $orphans++; } finally { $pools @@ -110,6 +115,15 @@ class DeleteOrphanedProjects extends Action $count = $count + $sum; } - Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans . ' orphans'); + Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans'); + } + + private function deleteProject(Database $dbForConsole, $projectId): void + { + try { + $dbForConsole->deleteDocument('projects', $projectId); + } catch (\Throwable $th) { + Console::error('Error when trying to delete project (' . $projectId . ') ' . $th->getMessage()); + } } } From 3654799cc781ca30dca502938e3a6e9d92c63d2d Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Thu, 9 Nov 2023 14:53:42 +0530 Subject: [PATCH 10/54] links ISO 8601, move params in messaging controllers --- app/controllers/api/databases.php | 2 +- app/controllers/api/messaging.php | 46 +++++++++++++++---------------- app/controllers/api/projects.php | 4 +-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index aaf30e00c3..3c372cb76e 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1571,7 +1571,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') diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 6545ebb25c..4634135ecf 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -507,12 +507,12 @@ App::post('/v1/messaging/providers/fcm') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) ->param('serverKey', '', new Text(0), 'FCM server key.') + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, bool $enabled, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $serverKey, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; $provider = new Document([ '$id' => $providerId, @@ -566,16 +566,16 @@ App::post('/v1/messaging/providers/apns') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) ->param('authKey', '', new Text(0), 'APNS authentication key.') ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.') ->param('teamId', '', new Text(0), 'APNS team ID.') ->param('bundleId', '', new Text(0), 'APNS bundle ID.') ->param('endpoint', '', new Text(0), 'APNS endpoint.') + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; $provider = new Document([ '$id' => $providerId, @@ -786,7 +786,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) - ->param('isEuRegion', null, new Boolean(), 'Set as eu region.', true) + ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) ->param('domain', '', new Text(0), 'Mailgun Domain.', true) @@ -1857,7 +1857,7 @@ App::delete('/v1/messaging/topics/:topicId') }); App::post('/v1/messaging/topics/:topicId/subscribers') - ->desc('Adds a subscriber to a topic.') + ->desc('Create a subscriber.') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.create') ->label('audits.resource', 'subscriber/{response.$id}') @@ -1870,9 +1870,9 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SUBSCRIBER) - ->param('subscriberId', '', new CustomId(), 'Subscriber ID. Choose a custom Topic ID or a new Topic ID.') - ->param('topicId', '', new UID(), 'Topic ID.') - ->param('targetId', '', new UID(), 'Target ID.') + ->param('subscriberId', '', new CustomId(), 'Subscriber ID. Choose a custom Subscriber ID or a new Subscriber ID.') + ->param('topicId', '', new UID(), 'Topic ID. The topic ID to subscribe to.') + ->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') @@ -1920,7 +1920,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') }); App::get('/v1/messaging/topics/:topicId/subscribers') - ->desc('List topic\'s subscribers.') + ->desc('List subscribers.') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) @@ -1930,7 +1930,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SUBSCRIBER_LIST) - ->param('topicId', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.') ->param('queries', [], new Subscribers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Providers::ALLOWED_ATTRIBUTES), true) ->inject('dbForProject') ->inject('response') @@ -2051,8 +2051,8 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') - ->desc('Get a topic\'s subscriber.') +App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') + ->desc('Get a subscriber.') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) @@ -2062,7 +2062,7 @@ App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SUBSCRIBER) - ->param('topicId', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.') ->param('subscriberId', '', new UID(), 'Subscriber ID.') ->inject('dbForProject') ->inject('response') @@ -2083,8 +2083,8 @@ App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); -App::delete('/v1/messaging/topics/:topicId/subscriber/:subscriberId') - ->desc('Delete a subscriber from a topic.') +App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') + ->desc('Delete a subscriber.') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.delete') ->label('audits.resource', 'subscriber/{request.subscriberId}') @@ -2097,7 +2097,7 @@ App::delete('/v1/messaging/topics/:topicId/subscriber/:subscriberId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('topicId', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.') ->param('subscriberId', '', new UID(), 'Subscriber ID.') ->inject('queueForEvents') ->inject('dbForProject') @@ -2150,7 +2150,7 @@ App::post('/v1/messaging/messages/email') ->param('description', '', new Text(256), 'Description for message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') @@ -2214,7 +2214,7 @@ App::post('/v1/messaging/messages/sms') ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') @@ -2284,7 +2284,7 @@ App::post('/v1/messaging/messages/push') ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') @@ -2531,7 +2531,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') @@ -2631,7 +2631,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') @@ -2730,7 +2730,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('project') diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index de9cc82b84..e59371cdfc 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1166,7 +1166,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) { @@ -1283,7 +1283,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) { From a029287e5a1cb958e59dde4f06268c304f1ab270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 13 Nov 2023 14:07:11 +0100 Subject: [PATCH 11/54] Implement health tresholds --- app/controllers/api/health.php | 131 +++++++++++++++--- .../Health/HealthCustomServerTest.php | 93 +++++++++++++ 2 files changed, 204 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index e0e26781cf..5d64e71526 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -14,6 +14,7 @@ use Utopia\Registry\Registry; use Utopia\Storage\Device; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; +use Utopia\Validator\Integer; use Utopia\Validator\Text; App::get('/v1/health') @@ -344,11 +345,20 @@ App::get('/v1/health/queue/webhooks') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/logs') @@ -362,11 +372,20 @@ App::get('/v1/health/queue/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::AUDITS_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/certificates') @@ -380,11 +399,20 @@ App::get('/v1/health/queue/certificates') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/builds') @@ -398,11 +426,20 @@ App::get('/v1/health/queue/builds') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::BUILDS_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/databases') @@ -417,11 +454,20 @@ App::get('/v1/health/queue/databases') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (string $name, Connection $queue, Response $response) { + ->action(function (string $name, int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client($name, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/deletes') @@ -435,11 +481,20 @@ App::get('/v1/health/queue/deletes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::DELETE_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/mails') @@ -453,11 +508,20 @@ App::get('/v1/health/queue/mails') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::MAILS_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/messaging') @@ -471,11 +535,20 @@ App::get('/v1/health/queue/messaging') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::MESSAGING_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/migrations') @@ -489,11 +562,20 @@ App::get('/v1/health/queue/migrations') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/queue/functions') @@ -507,11 +589,20 @@ App::get('/v1/health/queue/functions') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) + ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (Connection $queue, Response $response) { + ->action(function (int|string $treshold, Connection $queue, Response $response) { + $treshold = \intval($treshold); + $client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue); - $response->dynamic(new Document([ 'size' => $client->getQueueSize() ]), Response::MODEL_HEALTH_QUEUE); + $size = $client->getQueueSize(); + + if ($size >= $treshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + } + + $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); App::get('/v1/health/storage/local') diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index eafc961e8b..9a178ef1fa 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -138,6 +138,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/webhooks?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -155,6 +164,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/logs?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -172,6 +190,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/certificates?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -189,6 +216,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/functions?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -206,6 +242,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/builds?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -225,6 +270,18 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'database_db_main', + 'treshold' => '0' + ]); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -242,6 +299,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/deletes?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -259,6 +325,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/mails?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -276,6 +351,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/messaging?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } @@ -293,6 +377,15 @@ class HealthCustomServerTest extends Scope $this->assertIsInt($response['body']['size']); $this->assertLessThan(100, $response['body']['size']); + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/migrations?treshold=0', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(500, $response['headers']['status-code']); + return []; } From 967854d34af107b4b3c97d6360bf367150f61dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 13 Nov 2023 14:39:33 +0100 Subject: [PATCH 12/54] Fix grammar --- app/controllers/api/health.php | 100 +++++++++--------- .../Health/HealthCustomServerTest.php | 20 ++-- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 5d64e71526..90e080d5fa 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -345,17 +345,17 @@ App::get('/v1/health/queue/webhooks') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -372,17 +372,17 @@ App::get('/v1/health/queue/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::AUDITS_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -399,17 +399,17 @@ App::get('/v1/health/queue/certificates') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -426,17 +426,17 @@ App::get('/v1/health/queue/builds') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::BUILDS_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -454,17 +454,17 @@ App::get('/v1/health/queue/databases') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (string $name, int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (string $name, int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client($name, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -481,17 +481,17 @@ App::get('/v1/health/queue/deletes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::DELETE_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -508,17 +508,17 @@ App::get('/v1/health/queue/mails') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::MAILS_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -535,17 +535,17 @@ App::get('/v1/health/queue/messaging') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::MESSAGING_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -562,17 +562,17 @@ App::get('/v1/health/queue/migrations') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); @@ -589,17 +589,17 @@ App::get('/v1/health/queue/functions') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) - ->param('treshold', 5000, new Integer(true), 'Queue size treshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) + ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->inject('queue') ->inject('response') - ->action(function (int|string $treshold, Connection $queue, Response $response) { - $treshold = \intval($treshold); + ->action(function (int|string $threshold, Connection $queue, Response $response) { + $threshold = \intval($threshold); $client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue); $size = $client->getQueueSize(); - if ($size >= $treshold) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size treshold hit. Current size is {$size} and treshold is {$treshold}."); + if ($size >= $threshold) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); } $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 9a178ef1fa..8fa9faadd2 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -141,7 +141,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/webhooks?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/webhooks?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -167,7 +167,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/logs?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/logs?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -193,7 +193,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/certificates?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/certificates?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -219,7 +219,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/functions?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/functions?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -245,7 +245,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/builds?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/builds?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -278,7 +278,7 @@ class HealthCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'database_db_main', - 'treshold' => '0' + 'threshold' => '0' ]); $this->assertEquals(500, $response['headers']['status-code']); @@ -302,7 +302,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/deletes?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/deletes?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -328,7 +328,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/mails?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/mails?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -354,7 +354,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/messaging?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/messaging?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -380,7 +380,7 @@ class HealthCustomServerTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/health/queue/migrations?treshold=0', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/health/queue/migrations?threshold=0', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); From b9007a59564f38b54cf90ba0d88aa62060d62fff Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 13 Nov 2023 15:41:10 +0200 Subject: [PATCH 13/54] wrapping create stats query with Authorization::skip --- src/Appwrite/Usage/Calculators/TimeSeries.php | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Appwrite/Usage/Calculators/TimeSeries.php b/src/Appwrite/Usage/Calculators/TimeSeries.php index e0a12b443f..9ead48e93f 100644 --- a/src/Appwrite/Usage/Calculators/TimeSeries.php +++ b/src/Appwrite/Usage/Calculators/TimeSeries.php @@ -8,6 +8,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use InfluxDB\Database as InfluxDatabase; use DateTime; +use Utopia\Database\Validator\Authorization; use Utopia\Registry\Registry; class TimeSeries extends Calculator @@ -422,14 +423,16 @@ class TimeSeries extends Calculator */ private function createOrUpdateMetric(string $projectId, string $time, string $period, string $metric, int $value, int $type): void { + $id = \md5("{$time}_{$period}_{$metric}"); $project = $this->database->getDocument('projects', $projectId); $database = call_user_func($this->getProjectDB, $project); - try { - $document = $database->getDocument('stats', $id); - if ($document->isEmpty()) { - $database->createDocument('stats', new Document([ + Authorization::skip(function () use ($database, $id, $period, $time, $metric, $value, $type, $projectId) { + try { + $document = $database->getDocument('stats', $id); + if ($document->isEmpty()) { + $database->createDocument('stats', new Document([ '$id' => $id, 'period' => $period, 'time' => $time, @@ -437,21 +440,22 @@ class TimeSeries extends Calculator 'value' => $value, 'type' => $type, 'region' => $this->region, - ])); - } else { - $database->updateDocument( - 'stats', - $document->getId(), - $document->setAttribute('value', $value) - ); + ])); + } else { + $database->updateDocument( + 'stats', + $document->getId(), + $document->setAttribute('value', $value) + ); + } + } catch (\Exception $e) { // if projects are deleted this might fail + if (is_callable($this->errorHandler)) { + call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}"); + } else { + throw $e; + } } - } catch (\Exception $e) { // if projects are deleted this might fail - if (is_callable($this->errorHandler)) { - call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}"); - } else { - throw $e; - } - } + }); $this->register->get('pools')->reclaim(); } From dd0bf66212450041ab7518a0f958c8fdbb9c8166 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 13 Nov 2023 19:24:55 +0200 Subject: [PATCH 14/54] delete orphaned projects task --- .../Platform/Tasks/DeleteOrphanedProjects.php | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index aeaab4b248..2af9c54a6e 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -2,11 +2,9 @@ namespace Appwrite\Platform\Tasks; -use PHPMailer\PHPMailer\PHPMailer; use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; use Utopia\Platform\Action; use Utopia\Cache\Cache; use Utopia\CLI\Console; @@ -14,7 +12,6 @@ use Utopia\Database\Database; use Utopia\Pools\Group; use Utopia\Registry\Registry; use Utopia\Validator\Boolean; -use Utopia\Validator\Hostname; class DeleteOrphanedProjects extends Action { @@ -27,7 +24,7 @@ class DeleteOrphanedProjects extends Action { $this - ->desc('Get stats for projects') + ->desc('Delete orphaned projects') ->param('commit', false, new boolean(true), 'Commit project deletion', true) ->inject('pools') ->inject('cache') @@ -45,6 +42,9 @@ class DeleteOrphanedProjects extends Action Console::title('Delete orphaned projects V1'); Console::success(APP_NAME . ' Delete orphaned projects started'); + /** @var array $collections */ + $collectionsConfig = Config::getParam('collections', [])['projects'] ?? []; + /* Initialise new Utopia app */ $app = new App('UTC'); $console = $app->getResource('console'); @@ -55,6 +55,7 @@ class DeleteOrphanedProjects extends Action Console::success("Found a total of: {$totalProjects} projects"); $orphans = 1; + $cnt = 0; $count = 0; $limit = 30; $sum = 30; @@ -79,24 +80,43 @@ class DeleteOrphanedProjects extends Action $dbForProject = new Database($adapter, $cache); $dbForProject->setDefaultDatabase('appwrite'); $dbForProject->setNamespace('_' . $project->getInternalId()); - $collectionsCreated = $dbForProject->count(Database::METADATA); - if ($collectionsCreated === 0) { - if ($commit === true) { - Console::info('(' . $orphans . ') deleting project (' . $project->getId() . ')'); - $this->deleteProject($dbForConsole, $project->getId()); - } else { - Console::log('(' . $orphans . ') project (' . $project->getId() . ')'); + $collectionsCreated = 0; + $cnt++; + if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) { + $collectionsCreated = $dbForProject->count(Database::METADATA); + } + + $msg = '(' . $cnt . ') ignoring found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')'; + /** + * +2 == audit+abuse + */ + if ($collectionsCreated === (count($collectionsConfig) + 2)) { + Console::log($msg . ' ignoring....'); + continue; + } + + Console::log($msg); + + if ($collectionsCreated > 0) { + $collections = $dbForProject->find(Database::METADATA, []); + foreach ($collections as $collection) { + if ($commit) { + $dbForProject->deleteCollection($collection->getId()); + $dbForConsole->deleteCachedCollection($collection->getId()); + } + Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')'); } + } + if ($commit) { + $dbForConsole->deleteDocument('projects', $project->getId()); + $dbForConsole->deleteCachedDocument('projects', $project->getId()); + } + + Console::info('--Deleting project no (' . $project->getInternalId() . ')'); + $orphans++; - } } catch (\Throwable $th) { - if ($commit === true) { - Console::info('(' . $orphans . ') deleting project (' . $project->getId() . ')'); - $this->deleteProject($dbForConsole, $project->getId()); - } else { - Console::log('(' . $orphans . ') project (' . $project->getId() . ')'); - } - $orphans++; + Console::error('Error: ' . $th->getMessage()); } finally { $pools ->get($db) @@ -117,13 +137,4 @@ class DeleteOrphanedProjects extends Action Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans'); } - - private function deleteProject(Database $dbForConsole, $projectId): void - { - try { - $dbForConsole->deleteDocument('projects', $projectId); - } catch (\Throwable $th) { - Console::error('Error when trying to delete project (' . $projectId . ') ' . $th->getMessage()); - } - } } From 60c0f4c97393d8d665da5ab7d7b697fafa69abbc Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 13 Nov 2023 19:27:20 +0200 Subject: [PATCH 15/54] delete orphaned projects task --- src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 2af9c54a6e..2da3c91384 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -86,9 +86,9 @@ class DeleteOrphanedProjects extends Action $collectionsCreated = $dbForProject->count(Database::METADATA); } - $msg = '(' . $cnt . ') ignoring found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')'; + $msg = '(' . $cnt . ') found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')'; /** - * +2 == audit+abuse + * +2 = audit+abuse */ if ($collectionsCreated === (count($collectionsConfig) + 2)) { Console::log($msg . ' ignoring....'); From 3d9ee8bc521367d8242de2e4035970d44a9050da Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 13 Nov 2023 19:28:48 +0200 Subject: [PATCH 16/54] delete orphaned projects task --- src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 2da3c91384..ee9931bde4 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -25,7 +25,7 @@ class DeleteOrphanedProjects extends Action $this ->desc('Delete orphaned projects') - ->param('commit', false, new boolean(true), 'Commit project deletion', true) + ->param('commit', false, new Boolean(true), 'Commit project deletion', true) ->inject('pools') ->inject('cache') ->inject('dbForConsole') From f84a00dded4dd3a527540138d3495b09f0843517 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 14 Nov 2023 14:07:52 +0530 Subject: [PATCH 17/54] adds search attribute filter --- app/config/collections.php | 12 ++-- app/controllers/api/messaging.php | 44 +------------ app/init.php | 64 +++++++++++++++++++ composer.lock | 4 +- .../e2e/Services/Messaging/MessagingBase.php | 4 +- 5 files changed, 76 insertions(+), 52 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 33a60fce44..aa6afffb17 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1467,9 +1467,9 @@ $commonCollections = [ 'size' => 65535, 'signed' => true, 'required' => false, - 'default' => null, + 'default' => '', 'array' => false, - 'filters' => [], + 'filters' => ['providerSearch'], ], ], 'indexes' => [ @@ -1640,9 +1640,9 @@ $commonCollections = [ 'size' => 16384, 'signed' => true, 'required' => false, - 'default' => null, + 'default' => '', 'array' => false, - 'filters' => [], + 'filters' => ['messageSearch'], ], ], 'indexes' => [ @@ -1712,9 +1712,9 @@ $commonCollections = [ 'size' => 16384, 'signed' => true, 'required' => false, - 'default' => null, + 'default' => '', 'array' => false, - 'filters' => [], + 'filters' => ['topicSearch'], ], ], 'indexes' => [ diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 340961c3ac..7bac2abf88 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -68,7 +68,6 @@ App::post('/v1/messaging/providers/mailgun') 'provider' => 'mailgun', 'type' => 'email', 'enabled' => $enabled, - 'search' => $providerId . ' ' . $name . ' ' . 'mailgun' . ' ' . 'email', 'credentials' => [ 'apiKey' => $apiKey, 'domain' => $domain, @@ -133,7 +132,6 @@ App::post('/v1/messaging/providers/sendgrid') 'provider' => 'sendgrid', 'type' => 'email', 'enabled' => $enabled, - 'search' => $providerId . ' ' . $name . ' ' . 'sendgrid' . ' ' . 'email', 'credentials' => [ 'apiKey' => $apiKey, ], @@ -196,7 +194,6 @@ App::post('/v1/messaging/providers/msg91') 'name' => $name, 'provider' => 'msg91', 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'msg91' . ' ' . 'sms', 'enabled' => $enabled, 'credentials' => [ 'senderId' => $senderId, @@ -261,7 +258,6 @@ App::post('/v1/messaging/providers/telesign') 'name' => $name, 'provider' => 'telesign', 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'telesign' . ' ' . 'sms', 'enabled' => $enabled, 'credentials' => [ 'username' => $username, @@ -324,9 +320,8 @@ App::post('/v1/messaging/providers/textmagic') $provider = new Document([ '$id' => $providerId, 'name' => $name, - 'provider' => 'text-magic', + 'provider' => 'textmagic', 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'text-magic' . ' ' . 'sms', 'enabled' => $enabled, 'credentials' => [ 'username' => $username, @@ -391,7 +386,6 @@ App::post('/v1/messaging/providers/twilio') 'name' => $name, 'provider' => 'twilio', 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'twilio' . ' ' . 'sms', 'enabled' => $enabled, 'credentials' => [ 'accountSid' => $accountSid, @@ -456,7 +450,6 @@ App::post('/v1/messaging/providers/vonage') 'name' => $name, 'provider' => 'vonage', 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'vonage' . ' ' . 'sms', 'enabled' => $enabled, 'credentials' => [ 'apiKey' => $apiKey, @@ -519,7 +512,6 @@ App::post('/v1/messaging/providers/fcm') 'name' => $name, 'provider' => 'fcm', 'type' => 'push', - 'search' => $providerId . ' ' . $name . ' ' . 'fcm' . ' ' . 'push', 'enabled' => $enabled, 'credentials' => [ 'serverKey' => $serverKey, @@ -582,7 +574,6 @@ App::post('/v1/messaging/providers/apns') 'name' => $name, 'provider' => 'apns', 'type' => 'push', - 'search' => $providerId . ' ' . $name . ' ' . 'apns' . ' ' . 'push', 'enabled' => $enabled, 'credentials' => [ 'authKey' => $authKey, @@ -806,7 +797,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'mailgun' . ' ' . 'email'); } if (!empty($from)) { @@ -894,7 +884,6 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'sendgrid' . ' ' . 'email'); } if (!empty($from)) { @@ -973,7 +962,6 @@ App::patch('/v1/messaging/providers/msg91/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'msg91' . ' ' . 'sms'); } if (!empty($from)) { @@ -1058,7 +1046,6 @@ App::patch('/v1/messaging/providers/telesign/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'telesign' . ' ' . 'sms'); } if (!empty($from)) { @@ -1137,13 +1124,12 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') } $providerAttr = $provider->getAttribute('provider'); - if ($providerAttr !== 'text-magic') { + if ($providerAttr !== 'textmagic') { throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'textmagic' . ' ' . 'sms'); } if (!empty($from)) { @@ -1228,7 +1214,6 @@ App::patch('/v1/messaging/providers/twilio/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'twilio' . ' ' . 'sms'); } if (!empty($from)) { @@ -1313,7 +1298,6 @@ App::patch('/v1/messaging/providers/vonage/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'vonage' . ' ' . 'sms'); } if (!empty($from)) { @@ -1396,7 +1380,6 @@ App::patch('/v1/messaging/providers/fcm/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'fcm' . ' ' . 'push'); } if ($enabled === true || $enabled === false) { @@ -1470,7 +1453,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') if (!empty($name)) { $provider->setAttribute('name', $name); - $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'apns' . ' ' . 'push'); } if ($enabled === true || $enabled === false) { @@ -1588,9 +1570,6 @@ App::post('/v1/messaging/topics') if ($description) { $topic->setAttribute('description', $description); - $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $description); - } else { - $topic->setAttribute('search', $topic->getId() . ' ' . $name); } try { @@ -1796,16 +1775,6 @@ App::patch('/v1/messaging/topics/:topicId') $topic->setAttribute('description', $description); } - if (!empty($name) || !empty($description)) { - if (!empty($name) && !empty($description)) { - $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $description); - } elseif (!empty($name)) { - $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $topic->getAttribute('description')); - } else { - $topic->setAttribute('search', $topic->getId() . ' ' . $topic->getAttribute('name') . ' ' . $description); - } - } - $topic = $dbForProject->updateDocument('topics', $topicId, $topic); $queueForEvents @@ -2174,7 +2143,6 @@ App::post('/v1/messaging/messages/email') 'html' => $html, ], 'status' => $status, - 'search' => $messageId . ' ' . $description . ' ' . $subject, ])); if ($status === 'processing') { @@ -2236,7 +2204,6 @@ App::post('/v1/messaging/messages/sms') 'content' => $content, ], 'status' => $status, - 'search' => $messageId . ' ' . $description, ])); if ($status === 'processing') { @@ -2338,7 +2305,6 @@ App::post('/v1/messaging/messages/push') 'deliveryTime' => $deliveryTime, 'data' => $pushData, 'status' => $status, - 'search' => $messageId . ' ' . $description . ' ' . $title, ])); if ($status === 'processing') { @@ -2583,8 +2549,6 @@ App::patch('/v1/messaging/messages/email/:messageId') $message->setAttribute('description', $description); } - $message->setAttribute('search', $message->getId() . ' ' . $message->getAttribute('description') . ' ' . $data['subject'] . ' ' . $message->getAttribute('providerId')); - if (!empty($status)) { $message->setAttribute('status', $status); } @@ -2683,8 +2647,6 @@ App::patch('/v1/messaging/messages/sms/:messageId') $message->setAttribute('deliveryTime', $deliveryTime); } - $message->setAttribute('search', $message->getId() . ' ' . $message->getAttribute('description') . ' ' . $message->getAttribute('providerId')); - $message = $dbForProject->updateDocument('messages', $message->getId(), $message); if ($status === 'processing') { @@ -2814,8 +2776,6 @@ App::patch('/v1/messaging/messages/push/:messageId') $message->setAttribute('deliveryTime', $deliveryTime); } - $message->setAttribute('search', $message->getId() . ' ' . $message->getAttribute('description') . ' ' . $pushData['title'] . ' ' . $message->getAttribute('providerId')); - $message = $dbForProject->updateDocument('messages', $message->getId(), $message); if ($status === 'processing') { diff --git a/app/init.php b/app/init.php index 094ca412fe..2e0aa36eb0 100644 --- a/app/init.php +++ b/app/init.php @@ -557,6 +557,70 @@ Database::addFilter( 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', ''), + ]; + + if (\array_key_exists('subject', $message->getAttribute('data'))) { + $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['subject'], 'email']); + } else if (\array_key_exists('content', $message->getAttribute('data'))) { + $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['content'], 'sms']); + } else { + $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['title'], 'push']); + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + /** * DB Formats */ diff --git a/composer.lock b/composer.lock index 25f0c3964d..7cddef8715 100644 --- a/composer.lock +++ b/composer.lock @@ -1958,7 +1958,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/0.45.1" }, - "time": "2023-11-01T08:30:19+00:00" + "time": "2023-11-09T22:39:48+00:00" }, { "name": "utopia-php/domains", @@ -5822,5 +5822,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 78f2330b96..21d22827f0 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -353,7 +353,7 @@ trait MessagingBase */ public function testGetSubscriber(array $data) { - $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscribers/' . $data['subscriberId'], \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -496,7 +496,7 @@ trait MessagingBase */ public function testDeleteSubscriber(array $data) { - $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $data['topicId'] . '/subscribers/' . $data['subscriberId'], \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); From c5aaa670a90d2de4e29d51ce64e7abd82a54561b Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 14 Nov 2023 15:20:21 +0530 Subject: [PATCH 18/54] adds provider details in message search attribute --- app/controllers/api/messaging.php | 13 ++++----- app/init.php | 18 +++++++------ src/Appwrite/Platform/Workers/Messaging.php | 29 +++++++++++++++------ 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 7bac2abf88..2cc9639fbd 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1,5 +1,6 @@ label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender email address.') + ->param('from', '', new Email(), 'Sender email address.') ->param('apiKey', '', new Text(0), 'Sendgrid API key.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') @@ -180,7 +181,7 @@ App::post('/v1/messaging/providers/msg91') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender number.') + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->param('senderId', '', new Text(0), 'Msg91 Sender ID.') ->param('authKey', '', new Text(0), 'Msg91 Auth Key.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) @@ -244,7 +245,7 @@ App::post('/v1/messaging/providers/telesign') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender number.') + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->param('username', '', new Text(0), 'Telesign username.') ->param('password', '', new Text(0), 'Telesign password.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) @@ -308,7 +309,7 @@ App::post('/v1/messaging/providers/textmagic') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender number.') + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->param('username', '', new Text(0), 'Textmagic username.') ->param('apiKey', '', new Text(0), 'Textmagic apiKey.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) @@ -372,7 +373,7 @@ App::post('/v1/messaging/providers/twilio') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender number.') + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->param('accountSid', '', new Text(0), 'Twilio account secret ID.') ->param('authToken', '', new Text(0), 'Twilio authentication token.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) @@ -436,7 +437,7 @@ App::post('/v1/messaging/providers/vonage') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider 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('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Text(256), 'Sender number.') + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->param('apiKey', '', new Text(0), 'Vonage API key.') ->param('apiSecret', '', new Text(0), 'Vonage API secret.') ->param('enabled', true, new Boolean(), 'Set as enabled.', true) diff --git a/app/init.php b/app/init.php index 2e0aa36eb0..adac72ed50 100644 --- a/app/init.php +++ b/app/init.php @@ -568,7 +568,7 @@ Database::addFilter( $provider->getAttribute('type', '') ]; - $search = implode(' ', \array_filter($searchValues)); + $search = \implode(' ', \array_filter($searchValues)); return $search; }, @@ -586,7 +586,7 @@ Database::addFilter( $topic->getAttribute('description', ''), ]; - $search = implode(' ', \array_filter($searchValues)); + $search = \implode(' ', \array_filter($searchValues)); return $search; }, @@ -604,15 +604,17 @@ Database::addFilter( $message->getAttribute('status', ''), ]; - if (\array_key_exists('subject', $message->getAttribute('data'))) { - $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['subject'], 'email']); - } else if (\array_key_exists('content', $message->getAttribute('data'))) { - $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['content'], 'sms']); + $data = \json_decode($message->getAttribute('data', []), true); + + if (\array_key_exists('subject', $data)) { + $searchValues = \array_merge($searchValues, [$data['subject'], 'email']); + } elseif (\array_key_exists('content', $data)) { + $searchValues = \array_merge($searchValues, [$data['content'], 'sms']); } else { - $searchValues[] = \array_merge($searchValues, [$message->getAttribute('data')['title'], 'push']); + $searchValues = \array_merge($searchValues, [$data['title'], 'push']); } - $search = implode(' ', \array_filter($searchValues)); + $search = \implode(' ', \array_filter($searchValues)); return $search; }, diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 63e0ff651a..dcef84c2df 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -10,7 +10,6 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; use Utopia\Messaging\Adapters\SMS as SMSAdapter; use Utopia\Messaging\Adapters\SMS\Mock; use Utopia\Messaging\Adapters\SMS\Msg91; @@ -99,22 +98,31 @@ class Messaging extends Action $recipients = \array_merge($recipients, $targets); } + /** + * @var array> $identifiersByProviderId + */ + $identifiersByProviderId = []; + + /** + * @var Document[] $providers + */ $providers = []; foreach ($recipients as $recipient) { $providerId = $recipient->getAttribute('providerId'); - if (!isset($providers[$providerId])) { - $providers[$providerId] = []; + if (!isset($identifiersByProviderId[$providerId])) { + $identifiersByProviderId[$providerId] = []; } - $providers[$providerId][] = $recipient->getAttribute('identifier'); + $identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier'); } /** * @var array[] $results */ - $results = batch(\array_map(function ($providerId) use ($providers, $message, $dbForProject) { - return function () use ($providerId, $providers, $message, $dbForProject) { + $results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $message, $dbForProject) { + return function () use ($providerId, $identifiersByProviderId, $providers, $message, $dbForProject) { $provider = $dbForProject->getDocument('providers', $providerId); - $identifiers = $providers[$providerId]; + $providers[] = $provider; + $identifiers = $identifiersByProviderId[$providerId]; $adapter = match ($provider->getAttribute('type')) { 'sms' => $this->sms($provider), 'push' => $this->push($provider), @@ -154,7 +162,7 @@ class Messaging extends Action return $results; }; - }, \array_keys($providers))); + }, \array_keys($identifiersByProviderId))); $results = array_merge(...$results); @@ -172,6 +180,11 @@ class Messaging extends Action $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()); From be85b173e774c0f85ea95bc651b0829648c5859e Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:49:16 +0530 Subject: [PATCH 19/54] Only delete repositories linked to the particular project --- src/Appwrite/Platform/Workers/Deletes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 6bb8636695..2246816436 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -730,6 +730,7 @@ class Deletes extends Action */ Console::info("Deleting VCS repositories and comments linked to function " . $functionId); $this->deleteByGroup('repositories', [ + Query::equal('projectInternalId', [$projectInternalId]), Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('resourceType', ['function']), ], $dbForConsole, function (Document $document) use ($dbForConsole) { From 78eb5105e7ffc8ea4a204bf25c08e9c7e87c1884 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:08:09 +0530 Subject: [PATCH 20/54] Update projectInternalId var --- src/Appwrite/Platform/Workers/Deletes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 2246816436..4b888b9fd6 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -730,7 +730,7 @@ class Deletes extends Action */ Console::info("Deleting VCS repositories and comments linked to function " . $functionId); $this->deleteByGroup('repositories', [ - Query::equal('projectInternalId', [$projectInternalId]), + Query::equal('projectInternalId', [$project->getInternalId()]), Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('resourceType', ['function']), ], $dbForConsole, function (Document $document) use ($dbForConsole) { From 7ae614fe138c85ba85a6447b09a2619d5e1371fc Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 14 Nov 2023 18:14:07 +0530 Subject: [PATCH 21/54] adds provider type in target --- app/config/collections.php | 15 ++- app/config/errors.php | 5 + app/controllers/api/account.php | 7 +- app/controllers/api/messaging.php | 103 ++++++++++++------ app/controllers/api/teams.php | 3 +- app/controllers/api/users.php | 53 ++++++--- src/Appwrite/Extend/Exception.php | 1 + src/Appwrite/Platform/Workers/Messaging.php | 30 ++++- src/Appwrite/Utopia/Response/Model/Target.php | 6 + tests/e2e/Services/GraphQL/Base.php | 12 +- tests/e2e/Services/GraphQL/MessagingTest.php | 7 ++ tests/e2e/Services/GraphQL/UsersTest.php | 3 +- .../e2e/Services/Messaging/MessagingBase.php | 19 ++++ tests/e2e/Services/Users/UsersBase.php | 8 +- 14 files changed, 202 insertions(+), 70 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index aa6afffb17..bfa8192b74 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1845,7 +1845,7 @@ $commonCollections = [ 'filters' => [], ], [ - '$id' => ID::custom('providerId'), + '$id' => ID::custom('providerType'), 'type' => Database::VAR_STRING, 'format' => '', 'size' => Database::LENGTH_KEY, @@ -1855,13 +1855,24 @@ $commonCollections = [ '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' => true, + 'required' => false, 'default' => null, 'array' => false, 'filters' => [], diff --git a/app/config/errors.php b/app/config/errors.php index 1117f20e48..e915091940 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -781,6 +781,11 @@ return [ 'description' => 'Provider with the requested ID is of incorrect type: ', 'code' => 400, ], + Exception::PROVIDER_INTERNAL_UPDATE_DISABLED => [ + 'name' => Exception::PROVIDER_INTERNAL_UPDATE_DISABLED, + 'description' => 'Provider with the requested ID cannot be disabled.', + 'code' => 400, + ], /** Topic Errors */ Exception::TOPIC_NOT_FOUND => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 96c0483cca..77ff2a27e4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1240,6 +1240,7 @@ App::post('/v1/account/sessions/phone') Query::equal('internal', [true]), Query::equal('type', ['sms']) ])); + if ($provider === false || $provider->isEmpty()) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -1335,8 +1336,7 @@ App::post('/v1/account/sessions/phone') $target = $dbForProject->createDocument('targets', new Document([ 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), + 'providerType' => 'sms', 'identifier' => $phone, ])); } @@ -2959,8 +2959,7 @@ App::post('/v1/account/verification/phone') $target = $dbForProject->createDocument('targets', new Document([ 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), + 'providerType' => 'sms', 'identifier' => $user->getAttribute('phone'), ])); } diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 2cc9639fbd..f8d0ae16ec 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -806,14 +806,21 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if ($isEuRegion === true || $isEuRegion === false) { @@ -893,14 +900,17 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + if (!empty($apiKey)) { $provider->setAttribute('credentials', [ 'apiKey' => $apiKey, @@ -971,14 +981,17 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($senderId)) { @@ -1055,14 +1068,17 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -1139,14 +1155,17 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -1223,14 +1242,17 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($accountSid)) { @@ -1307,14 +1329,17 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($apiKey)) { @@ -1383,14 +1408,17 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider->setAttribute('name', $name); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + if (!empty($serverKey)) { $provider->setAttribute('credentials', ['serverKey' => $serverKey]); } @@ -1456,14 +1484,17 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('name', $name); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if ($internal === true) { $provider->setAttribute('internal', $internal); } + if ($enabled === true || $enabled === false) { + if ($provider->getAttribute('internal') === true && $enabled === false) { + throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + } + $provider->setAttribute('enabled', $enabled); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($authKey)) { diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index a4ad61733f..b19080e894 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -650,8 +650,7 @@ App::post('/v1/teams/:teamId/memberships') $target = $dbForProject->createDocument('targets', new Document([ 'userId' => $invitee->getId(), 'userInternalId' => $invitee->getInternalId(), - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), + 'providerType' => 'sms', 'identifier' => $phone, ])); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 71deded08f..d9968c83a7 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -394,18 +394,25 @@ App::post('/v1/users/:userId/targets') ->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('targetId', '', new UID(), 'Target ID.') - ->param('providerId', '', new UID(), 'Provider ID.') + ->param('providerType', '', new WhiteList(['email', 'sms', '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) ->inject('queueForEvents') ->inject('response') ->inject('dbForProject') - ->action(function (string $userId, string $targetId, string $providerId, string $identifier, Event $queueForEvents, Response $response, Database $dbForProject) { - $provider = $dbForProject->getDocument('providers', $providerId); + ->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, Event $queueForEvents, Response $response, Database $dbForProject) { + $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; - if ($provider->isEmpty()) { - throw new Exception(Exception::PROVIDER_NOT_FOUND); + $provider = new Document(); + + if ($providerType === 'push') { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } } $user = $dbForProject->getDocument('users', $userId); @@ -423,8 +430,9 @@ App::post('/v1/users/:userId/targets') try { $target = $dbForProject->createDocument('targets', new Document([ '$id' => $targetId, - 'providerId' => $providerId, - 'providerInternalId' => $provider->getInternalId(), + 'providerId' => $providerId ?? null, + 'providerInternalId' => $provider->getInternalId() ?? null, + 'providerType' => $providerType, 'userId' => $userId, 'userInternalId' => $user->getInternalId(), 'identifier' => $identifier, @@ -1223,8 +1231,8 @@ App::patch('/v1/users/:userId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::patch('/v1/users/:userId/targets/:targetId/identifier') - ->desc('Update user target\'s identifier') +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}') @@ -1232,19 +1240,19 @@ App::patch('/v1/users/:userId/targets/:targetId/identifier') ->label('scope', 'targets.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'users') - ->label('sdk.method', 'updateTargetIdentifier') - ->label('sdk.description', '/docs/references/users/update-target-identifier.md') + ->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.)') + ->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true) + ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true) ->inject('queueForEvents') ->inject('response') ->inject('dbForProject') - ->action(function (string $userId, string $targetId, string $identifier, Event $queueForEvents, Response $response, Database $dbForProject) { - + ->action(function (string $userId, string $targetId, string $providerId, string $identifier, Event $queueForEvents, Response $response, Database $dbForProject) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { @@ -1261,7 +1269,20 @@ App::patch('/v1/users/:userId/targets/:targetId/identifier') throw new Exception(Exception::USER_TARGET_NOT_FOUND); } - $target->setAttribute('identifier', $identifier); + if ($identifier) { + $target->setAttribute('identifier', $identifier); + } + + if ($providerId) { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $target->setAttribute('providerId', $provider->getId()); + $target->setAttribute('providerInternalId', $provider->getInternalId()); + } $target = $dbForProject->updateDocument('targets', $target->getId(), $target); $dbForProject->deleteCachedDocument('users', $user->getId()); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d887a5a520..b7043a9738 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -236,6 +236,7 @@ class Exception extends \Exception 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'; diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index dcef84c2df..68bae1b4d0 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -98,6 +98,11 @@ class Messaging extends Action $recipients = \array_merge($recipients, $targets); } + $internalProvider = $dbForProject->findOne('providers', [ + Query::equal('internal', [true]), + Query::equal('type', [$recipients[0]->getAttribute('providerType')]), + ]); + /** * @var array> $identifiersByProviderId */ @@ -109,6 +114,11 @@ class Messaging extends Action $providers = []; foreach ($recipients as $recipient) { $providerId = $recipient->getAttribute('providerId'); + + if (!$providerId) { + $providerId = $internalProvider->getId(); + } + if (!isset($identifiersByProviderId[$providerId])) { $identifiersByProviderId[$providerId] = []; } @@ -118,17 +128,26 @@ class Messaging extends Action /** * @var array[] $results */ - $results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $message, $dbForProject) { - return function () use ($providerId, $identifiersByProviderId, $providers, $message, $dbForProject) { - $provider = $dbForProject->getDocument('providers', $providerId); + $results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) { + return function () use ($providerId, $identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) { + $provider = new Document(); + + if ($internalProvider->getId() === $providerId) { + $provider = $internalProvider; + } else { + $provider = $dbForProject->getDocument('providers', $providerId); + } + $providers[] = $provider; $identifiers = $identifiersByProviderId[$providerId]; + $adapter = match ($provider->getAttribute('type')) { 'sms' => $this->sms($provider), 'push' => $this->push($provider), 'email' => $this->email($provider), default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) }; + $maxBatchSize = $adapter->getMaxMessagesPerRequest(); $batches = \array_chunk($identifiers, $maxBatchSize); $batchIndex = 0; @@ -139,12 +158,14 @@ class Messaging extends Action $deliveryErrors = []; $messageData = clone $message; $messageData->setAttribute('to', $batch); + $data = match ($provider->getAttribute('type')) { 'sms' => $this->buildSMSMessage($messageData, $provider), 'push' => $this->buildPushMessage($messageData), 'email' => $this->buildEmailMessage($messageData, $provider), default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) }; + try { $adapter->send($data); $deliveredTotal += \count($batch); @@ -168,10 +189,12 @@ class Messaging extends Action $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) { @@ -179,6 +202,7 @@ class Messaging extends Action } else { $message->setAttribute('status', 'sent'); } + $message->removeAttribute('to'); foreach ($providers as $provider) { diff --git a/src/Appwrite/Utopia/Response/Model/Target.php b/src/Appwrite/Utopia/Response/Model/Target.php index c6c5929ee3..f6346f409a 100644 --- a/src/Appwrite/Utopia/Response/Model/Target.php +++ b/src/Appwrite/Utopia/Response/Model/Target.php @@ -41,6 +41,12 @@ class Target extends Model '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' => 'email', + ]) ->addRule('identifier', [ 'type' => self::TYPE_STRING, 'description' => 'The target identifier.', diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 93fd6cdc74..b57b680674 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -934,10 +934,11 @@ trait Base } }'; case self::$CREATE_USER_TARGET: - return 'mutation createUserTarget($userId: String!, $targetId: String!, $providerId: String!, $identifier: String!){ - usersCreateTarget(userId: $userId, targetId: $targetId, providerId: $providerId, identifier: $identifier) { + 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 } @@ -949,6 +950,7 @@ trait Base targets { _id userId + providerType providerId identifier } @@ -959,15 +961,17 @@ trait Base usersGetTarget(userId: $userId, targetId: $targetId) { _id userId + providerType providerId identifier } }'; case self::$UPDATE_USER_TARGET: - return 'mutation updateUserTarget($userId: String!, $targetId: String!, $identifier: String!){ - usersUpdateTargetIdentifier(userId: $userId, targetId: $targetId, identifier: $identifier) { + 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 } diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index c0322372ba..57ed825a5c 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -395,6 +395,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'userId' => $userId, 'providerId' => $providerId, 'identifier' => 'token', @@ -604,6 +605,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, @@ -755,6 +757,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, @@ -916,6 +919,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'sms', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, @@ -1063,6 +1067,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'sms', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, @@ -1219,6 +1224,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'push', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, @@ -1363,6 +1369,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'targetId' => ID::unique(), + 'providerType' => 'push', 'userId' => $user['body']['data']['usersCreate']['_id'], 'providerId' => $providerId, 'identifier' => $to, diff --git a/tests/e2e/Services/GraphQL/UsersTest.php b/tests/e2e/Services/GraphQL/UsersTest.php index d750de5834..59c0c4a805 100644 --- a/tests/e2e/Services/GraphQL/UsersTest.php +++ b/tests/e2e/Services/GraphQL/UsersTest.php @@ -78,6 +78,7 @@ class UsersTest extends Scope 'variables' => [ 'targetId' => ID::unique(), 'userId' => $user['_id'], + 'providerType' => 'email', 'providerId' => $providerId, 'identifier' => 'identifier', ] @@ -479,7 +480,7 @@ class UsersTest extends Scope ], $this->getHeaders()), $graphQLPayload); $this->assertEquals(200, $target['headers']['status-code']); - $this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTargetIdentifier']['identifier']); + $this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTarget']['identifier']); } public function testDeleteUserSessions() diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 21d22827f0..707ba2f7b5 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -317,6 +317,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'providerId' => $provider['body']['$id'], 'identifier' => 'my-token', ]); @@ -558,6 +559,7 @@ trait MessagingBase 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), 'from' => $from ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -570,6 +572,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -593,6 +596,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); @@ -682,6 +686,7 @@ trait MessagingBase 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), 'from' => $from ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -694,6 +699,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -717,6 +723,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'email', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); @@ -801,6 +808,7 @@ trait MessagingBase 'authKey' => $authKey, 'from' => $from ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -813,6 +821,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -836,6 +845,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'sms', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); @@ -922,6 +932,7 @@ trait MessagingBase 'authKey' => $authKey, 'from' => $from ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -934,6 +945,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -957,6 +969,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'sms', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); @@ -1036,6 +1049,7 @@ trait MessagingBase 'name' => 'FCM-1', 'serverKey' => $serverKey, ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -1048,6 +1062,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -1071,6 +1086,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'push', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); @@ -1154,6 +1170,7 @@ trait MessagingBase 'name' => 'FCM-2', 'serverKey' => $serverKey, ]); + $this->assertEquals(201, $provider['headers']['status-code']); // Create Topic @@ -1166,6 +1183,7 @@ trait MessagingBase 'name' => 'topic1', 'description' => 'Test Topic' ]); + $this->assertEquals(201, $topic['headers']['status-code']); // Create User @@ -1189,6 +1207,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'targetId' => ID::unique(), + 'providerType' => 'push', 'providerId' => $provider['body']['$id'], 'identifier' => $to, ]); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 9ca00aa5df..c9cb17d7b5 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1232,7 +1232,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'providerId' => 'unique()', + 'providerId' => ID::unique(), 'name' => 'Sengrid1', 'apiKey' => 'my-apikey', 'from' => 'from@domain.com', @@ -1244,6 +1244,7 @@ trait UsersBase ], $this->getHeaders()), [ 'targetId' => ID::unique(), 'providerId' => $provider['body']['$id'], + 'providerType' => 'email', 'identifier' => 'my-token', ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -1257,7 +1258,7 @@ trait UsersBase */ public function testUpdateUserTarget(array $data): array { - $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/targets/' . $data['$id'] . '/identifier', array_merge([ + $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()), [ @@ -1303,11 +1304,14 @@ trait UsersBase '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(0, $response['body']['total']); } From 4f2f76db2239510fe833828daefc9e628ddc2a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 14:11:54 +0100 Subject: [PATCH 22/54] Improve deletion relation with IDs --- src/Appwrite/Platform/Workers/Deletes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 4b888b9fd6..b95a13a12e 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -735,10 +735,10 @@ class Deletes extends Action Query::equal('resourceType', ['function']), ], $dbForConsole, function (Document $document) use ($dbForConsole) { $providerRepositoryId = $document->getAttribute('providerRepositoryId', ''); - $projectId = $document->getAttribute('projectId', ''); + $projectInternalId = $document->getAttribute('projectInternalId', ''); $this->deleteByGroup('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), - Query::equal('projectId', [$projectId]), + Query::equal('projectInternalId', [$projectInternalId]), ], $dbForConsole); }); From fb6455b783c554536aac864753301b840708e432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 14:43:33 +0100 Subject: [PATCH 23/54] Add script to patch missing repos documents --- Dockerfile | 1 + bin/patch-recreate-repositories-documents | 3 + .../patchRecreateRepositoriesDocuments.php | 141 ++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 bin/patch-recreate-repositories-documents create mode 100644 src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php diff --git a/Dockerfile b/Dockerfile index 33b1434659..059c499bd9 100755 --- a/Dockerfile +++ b/Dockerfile @@ -100,6 +100,7 @@ RUN chmod +x /usr/local/bin/doctor && \ RUN chmod +x /usr/local/bin/hamster && \ chmod +x /usr/local/bin/volume-sync && \ chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \ + chmod +x /usr/local/bin/patch-recreate-repositories-documents && \ chmod +x /usr/local/bin/patch-delete-project-collections && \ chmod +x /usr/local/bin/delete-orphaned-projects && \ chmod +x /usr/local/bin/clear-card-cache && \ diff --git a/bin/patch-recreate-repositories-documents b/bin/patch-recreate-repositories-documents new file mode 100644 index 0000000000..8c6c4157f4 --- /dev/null +++ b/bin/patch-recreate-repositories-documents @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@ \ No newline at end of file diff --git a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php new file mode 100644 index 0000000000..5749380d0a --- /dev/null +++ b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php @@ -0,0 +1,141 @@ +desc('Recreate missing repositories in consoleDB from projectDBs. They can be missing if you used Appwrite 1.4.10 or 1.4.11, and deleted a function.') + ->param('after', '', new Text(36), 'After cursor', true) + ->param('projectId', '', new Text(36), 'Select project to validate', true) + ->inject('dbForConsole') + ->inject('getProjectDB') + ->callback(fn ($after, $projectId, $dbForConsole, $getProjectDB) => $this->action($after, $projectId, $dbForConsole, $getProjectDB)); + } + + public function action($after, $projectId, Database $dbForConsole, callable $getProjectDB): void + { + Console::info("Starting the patch"); + + $startTime = microtime(true); + + if(!empty($projectId)) { + $project = $dbForConsole->getDocument('projects', $projectId); + $dbForProject = call_user_func($getProjectDB, $project); + $this->recreateRepositories($dbForConsole, $dbForProject, $project); + } else { + $queries = []; + if(!empty($after)) { + Console::info("Iterating remaining projects after project with ID {$after}"); + $project = $dbForConsole->getDocument('projects', $after); + $queries = [Query::cursorAfter($project)]; + } else { + Console::info("Iterating all projects"); + } + $this->foreachDocument($dbForConsole, 'projects', $queries, function(Document $project) use($getProjectDB, $dbForConsole){ + $dbForProject = call_user_func($getProjectDB, $project); + $this->recreateRepositories($dbForConsole, $dbForProject, $project); + }); + } + + $endTime = microtime(true); + $timeTaken = $endTime - $startTime; + + $hours = (int)($timeTaken / 3600); + $timeTaken -= $hours * 3600; + $minutes = (int)($timeTaken / 60); + $timeTaken -= $minutes * 60; + $seconds = (int)$timeTaken; + $milliseconds = ($timeTaken - $seconds) * 1000; + Console::info("Recreate patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds)"); + } + + protected function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void + { + $limit = 1000; + $results = []; + $sum = $limit; + $latestDocument = null; + + while ($sum === $limit) { + $newQueries = $queries; + + if ($latestDocument != null) { + array_unshift($newQueries, Query::cursorAfter($latestDocument)); + } + $newQueries[] = Query::limit($limit); + $results = $database->find($collection, $newQueries); + + if (empty($results)) { + return; + } + + $sum = count($results); + + foreach ($results as $document) { + if (is_callable($callback)) { + $callback($document); + } + } + $latestDocument = $results[array_key_last($results)]; + } + } + + public function recreateRepositories(Database $dbForConsole, Database $dbForProject, Document $project): void + { + $projectId = $project->getId(); + Console::log("Running patch for project {$projectId}"); + + $this->foreachDocument($dbForProject, 'functions', [], function(Document $function) use ($dbForConsole, $project) { + $isConnected = !empty($function->getAttribute('providerRepositoryId', '')); + + if($isConnected) { + $repository = $dbForConsole->getDocument('repositories', $function->getAttribute('repositoryId', '')); + + if($repository->isEmpty()) { + $projectId = $project->getId(); + $functionId = $function->getId(); + Console::success("Recreating repositories document for project ID {$projectId}, function ID {$functionId}"); + + $repository = $dbForConsole->createDocument('repositories', new Document([ + '$id' => ID::unique(), + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'installationId' => $function->getAttribute('installationId', ''), + 'installationInternalId' => $function->getAttribute('installationInternalId', ''), + 'projectId' => $project->getId(), + 'projectInternalId' => $project->getInternalId(), + 'providerRepositoryId' => $function->getAttribute('providerRepositoryId', ''), + 'resourceId' => $function->getId(), + 'resourceInternalId' => $function->getInternalId(), + 'resourceType' => 'function', + 'providerPullRequestIds' => [] + ])); + } + } + }); + } +} From c133bccfa93b52cca8dd9e0210f4d2ee8fc2d921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 15:06:58 +0100 Subject: [PATCH 24/54] Finish recreate repos docs script --- .env | 38 ++++++++++++++++--- src/Appwrite/Platform/Services/Tasks.php | 2 + .../patchRecreateRepositoriesDocuments.php | 32 ++++++++++------ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/.env b/.env index ad551e705a..759ddcc051 100644 --- a/.env +++ b/.env @@ -90,12 +90,38 @@ _APP_GRAPHQL_MAX_COMPLEXITY=250 _APP_GRAPHQL_MAX_DEPTH=3 _APP_DOCKER_HUB_USERNAME= _APP_DOCKER_HUB_PASSWORD= -_APP_VCS_GITHUB_APP_NAME= -_APP_VCS_GITHUB_PRIVATE_KEY=disabled -_APP_VCS_GITHUB_APP_ID= -_APP_VCS_GITHUB_CLIENT_ID= -_APP_VCS_GITHUB_CLIENT_SECRET= -_APP_VCS_GITHUB_WEBHOOK_SECRET= +_APP_VCS_GITHUB_APP_NAME=appwrite-generated-on-22-5-2023 +_APP_VCS_GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApUP3k7ratjenwOA1AtnmvZbNIm9Mhu181ryrnb7M1z1/OLUo +jZ1iS8u1K/GQtoqCNI8RR38rBXW1hzfC34qAL3XrJr4caK+AKzV7B4y9e2Zn+VlF +ZZ2rknNPlRLCwll6V60M7suqLONUADs6bvEs8GIR926gwNi8SmomtIj8k8UZFW00 +9L0OM0pvajJKASPynZ7jXfxic5Yu060BQq+IAtOkOaxyBF1xiGQRBuAt2movTI2X +4PwGcSCy7YD6v2DRgHIoYehtdWx0DrbRgCXsmwMin5BR/7ZhBSJE7YVH9vSqWThe +V8VmLKPiZZC5ZW2Vf28kM/FlkCgjfd0tNXs8lQIDAQABAoIBAQCWaFYhODSnE93z +ttnoH2JVd7J4PW0be3ZbhNh3t1d8KPbpKE6hG/SC4QGg3bgDuekoZnCmbkE8NdWh +G4maotVo3FvIJct7JwZxzLmMtHUaoqfMEogLJEUrAxERrkJcWMz0kIUtq2PUeIxR +rZXPtGVe3RJW63MYL+ilnRhexDGDVa5I50jFCTT5GXXHl6tw6tei1rDc2kI7pb3k +BGAxsRGXUK6Di9EmR2Z8xFHvM3LeuSWR2WalR8+abn661P3J+InMqh0s+TBB86kH +6NUxoWKSCXjPfMWj15M0Wwm4CBlzgc6GS106hmHPi3YAQLCV4x0l4qmq+bdsQOnN +VAOnPAohAoGBANidEYsMm5jaTklFnIExBkm5OHcKRuMgplMoEsVE/dglBPCJB3Uf +JUmhMRVazztDGD0ciImypt4j9klR4zqSQv4Aa8OdwW/jcuNPm3qQrdSm86iP2Lbe +V9DNKK0vQ/3srwIYxl9qabOLaQrKAeiPxfUuL91iyDIYtsPPIgfvVGfdAoGBAMNQ +v5wruODDgf0mMm1nA9LNHlkMi8uaVvxFAhjWtmPOH4FHXgzMDGC6syvL+d1XIdtF +tA/j/f/A1zFsYzeZRVBqVmpd8rvRzFTaRrBgLjI/vxmbJ7syr9rT7iZYvFYUv3c+ +mr8m5AIGULiGmMYnSWttIi2prlA17FC5Qp/lq/gZAoGAVrBlePSOwNl9Qy2suLda +AN8zjdB7FiLW7ai3+mLmBD6sf2cXqPPSBGmSLy2sidcMOEjXC+SHi5dw1V8ERUiL +rwOUHTFhXNn1/Kq7Wo3UQ6qdEPSgkm7hThsNEGI+H709POWVXlJEAyrj2wGFSgFg +BAN7/GmwHPxvCGY5BFvvt7ECgYBIWqOA4RmN+h8vfnTz3lOmReJWLrWi6TwMHCxY +s0HB21wEckG/D+AN/Vvef6PCgULDjiDUOiugEPonDvX6ZMcusRXuNXt0ZJYDYREK +ybaTWtYaUEX5rR9EO3pfrkOmx+zd6c09vtR8g4ZntUTnMyqZp0YgEFnI0REIHnk1 +7sk0EQKBgGTzNU9Ir6cEZh4j+Qf5rA38bejkD8aRAYg5ozQcbNRJnrC7QnpmZPeD +X/E9MZ6wj1BVXEn2oNC63n3QB+B8OhrIDAYDbnaCLzVDl/BTuom3uTCYk0beKncz +AurSDpc15RFYjqn0DPBSii/DTaQIz0Rg+seZrOp5Ii2LrSlsnDPf +-----END RSA PRIVATE KEY-----" +_APP_VCS_GITHUB_APP_ID=337303 +_APP_VCS_GITHUB_CLIENT_ID=Iv1.306ee38582d3f948 +_APP_VCS_GITHUB_CLIENT_SECRET=eafc638eaeebe95c0db0fdf59a0a99b9e41832eb +_APP_VCS_GITHUB_WEBHOOK_SECRET=gzbrfuenqodiefbrg39u _APP_MIGRATIONS_FIREBASE_CLIENT_ID= _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET= _APP_ASSISTANT_OPENAI_API_KEY= \ No newline at end of file diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index e725ff5f3e..28d7046dd1 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -19,6 +19,7 @@ use Appwrite\Platform\Tasks\VolumeSync; use Appwrite\Platform\Tasks\CalcTierStats; use Appwrite\Platform\Tasks\Upgrade; use Appwrite\Platform\Tasks\DeleteOrphanedProjects; +use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments; class Tasks extends Service { @@ -42,6 +43,7 @@ class Tasks extends Service ->addAction(Specs::getName(), new Specs()) ->addAction(CalcTierStats::getName(), new CalcTierStats()) ->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects()) + ->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments()) ; } diff --git a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php index 5749380d0a..4d04802f50 100644 --- a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php +++ b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php @@ -3,8 +3,6 @@ namespace Appwrite\Platform\Tasks; use Utopia\Platform\Action; -use Appwrite\Event\Certificate; -use Utopia\App; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; @@ -12,10 +10,9 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; -use Utopia\Validator\Hostname; use Utopia\Validator\Text; -class patchRecreateRepositoriesDocuments extends Action +class PatchRecreateRepositoriesDocuments extends Action { public static function getName(): string { @@ -34,25 +31,25 @@ class patchRecreateRepositoriesDocuments extends Action } public function action($after, $projectId, Database $dbForConsole, callable $getProjectDB): void - { + { Console::info("Starting the patch"); $startTime = microtime(true); - if(!empty($projectId)) { + if (!empty($projectId)) { $project = $dbForConsole->getDocument('projects', $projectId); $dbForProject = call_user_func($getProjectDB, $project); $this->recreateRepositories($dbForConsole, $dbForProject, $project); } else { $queries = []; - if(!empty($after)) { + if (!empty($after)) { Console::info("Iterating remaining projects after project with ID {$after}"); $project = $dbForConsole->getDocument('projects', $after); $queries = [Query::cursorAfter($project)]; } else { Console::info("Iterating all projects"); } - $this->foreachDocument($dbForConsole, 'projects', $queries, function(Document $project) use($getProjectDB, $dbForConsole){ + $this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole) { $dbForProject = call_user_func($getProjectDB, $project); $this->recreateRepositories($dbForConsole, $dbForProject, $project); }); @@ -106,13 +103,13 @@ class patchRecreateRepositoriesDocuments extends Action $projectId = $project->getId(); Console::log("Running patch for project {$projectId}"); - $this->foreachDocument($dbForProject, 'functions', [], function(Document $function) use ($dbForConsole, $project) { + $this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $dbForConsole, $project) { $isConnected = !empty($function->getAttribute('providerRepositoryId', '')); - if($isConnected) { + if ($isConnected) { $repository = $dbForConsole->getDocument('repositories', $function->getAttribute('repositoryId', '')); - if($repository->isEmpty()) { + if ($repository->isEmpty()) { $projectId = $project->getId(); $functionId = $function->getId(); Console::success("Recreating repositories document for project ID {$projectId}, function ID {$functionId}"); @@ -134,6 +131,19 @@ class patchRecreateRepositoriesDocuments extends Action 'resourceType' => 'function', 'providerPullRequestIds' => [] ])); + + $function = $dbForProject->updateDocument('functions', $function->getId(), $function + ->setAttribute('repositoryId', $repository->getId()) + ->setAttribute('repositoryInternalId', $repository->getInternalId())); + + $this->foreachDocument($dbForProject, 'deployments', [ + Query::equal('resourceInternalId', [$function->getInternalId()]), + Query::equal('resourceType', ['functions']) + ], function (Document $deployment) use ($dbForProject, $repository) { + $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment + ->setAttribute('repositoryId', $repository->getId()) + ->setAttribute('repositoryInternalId', $repository->getInternalId())); + }); } } }); From 2572cb43c2007829bb4f91dd58fc0a680cc3f7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 15:11:17 +0100 Subject: [PATCH 25/54] Code cleanup --- .env | 38 ++++++-------------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/.env b/.env index 759ddcc051..ad551e705a 100644 --- a/.env +++ b/.env @@ -90,38 +90,12 @@ _APP_GRAPHQL_MAX_COMPLEXITY=250 _APP_GRAPHQL_MAX_DEPTH=3 _APP_DOCKER_HUB_USERNAME= _APP_DOCKER_HUB_PASSWORD= -_APP_VCS_GITHUB_APP_NAME=appwrite-generated-on-22-5-2023 -_APP_VCS_GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEApUP3k7ratjenwOA1AtnmvZbNIm9Mhu181ryrnb7M1z1/OLUo -jZ1iS8u1K/GQtoqCNI8RR38rBXW1hzfC34qAL3XrJr4caK+AKzV7B4y9e2Zn+VlF -ZZ2rknNPlRLCwll6V60M7suqLONUADs6bvEs8GIR926gwNi8SmomtIj8k8UZFW00 -9L0OM0pvajJKASPynZ7jXfxic5Yu060BQq+IAtOkOaxyBF1xiGQRBuAt2movTI2X -4PwGcSCy7YD6v2DRgHIoYehtdWx0DrbRgCXsmwMin5BR/7ZhBSJE7YVH9vSqWThe -V8VmLKPiZZC5ZW2Vf28kM/FlkCgjfd0tNXs8lQIDAQABAoIBAQCWaFYhODSnE93z -ttnoH2JVd7J4PW0be3ZbhNh3t1d8KPbpKE6hG/SC4QGg3bgDuekoZnCmbkE8NdWh -G4maotVo3FvIJct7JwZxzLmMtHUaoqfMEogLJEUrAxERrkJcWMz0kIUtq2PUeIxR -rZXPtGVe3RJW63MYL+ilnRhexDGDVa5I50jFCTT5GXXHl6tw6tei1rDc2kI7pb3k -BGAxsRGXUK6Di9EmR2Z8xFHvM3LeuSWR2WalR8+abn661P3J+InMqh0s+TBB86kH -6NUxoWKSCXjPfMWj15M0Wwm4CBlzgc6GS106hmHPi3YAQLCV4x0l4qmq+bdsQOnN -VAOnPAohAoGBANidEYsMm5jaTklFnIExBkm5OHcKRuMgplMoEsVE/dglBPCJB3Uf -JUmhMRVazztDGD0ciImypt4j9klR4zqSQv4Aa8OdwW/jcuNPm3qQrdSm86iP2Lbe -V9DNKK0vQ/3srwIYxl9qabOLaQrKAeiPxfUuL91iyDIYtsPPIgfvVGfdAoGBAMNQ -v5wruODDgf0mMm1nA9LNHlkMi8uaVvxFAhjWtmPOH4FHXgzMDGC6syvL+d1XIdtF -tA/j/f/A1zFsYzeZRVBqVmpd8rvRzFTaRrBgLjI/vxmbJ7syr9rT7iZYvFYUv3c+ -mr8m5AIGULiGmMYnSWttIi2prlA17FC5Qp/lq/gZAoGAVrBlePSOwNl9Qy2suLda -AN8zjdB7FiLW7ai3+mLmBD6sf2cXqPPSBGmSLy2sidcMOEjXC+SHi5dw1V8ERUiL -rwOUHTFhXNn1/Kq7Wo3UQ6qdEPSgkm7hThsNEGI+H709POWVXlJEAyrj2wGFSgFg -BAN7/GmwHPxvCGY5BFvvt7ECgYBIWqOA4RmN+h8vfnTz3lOmReJWLrWi6TwMHCxY -s0HB21wEckG/D+AN/Vvef6PCgULDjiDUOiugEPonDvX6ZMcusRXuNXt0ZJYDYREK -ybaTWtYaUEX5rR9EO3pfrkOmx+zd6c09vtR8g4ZntUTnMyqZp0YgEFnI0REIHnk1 -7sk0EQKBgGTzNU9Ir6cEZh4j+Qf5rA38bejkD8aRAYg5ozQcbNRJnrC7QnpmZPeD -X/E9MZ6wj1BVXEn2oNC63n3QB+B8OhrIDAYDbnaCLzVDl/BTuom3uTCYk0beKncz -AurSDpc15RFYjqn0DPBSii/DTaQIz0Rg+seZrOp5Ii2LrSlsnDPf ------END RSA PRIVATE KEY-----" -_APP_VCS_GITHUB_APP_ID=337303 -_APP_VCS_GITHUB_CLIENT_ID=Iv1.306ee38582d3f948 -_APP_VCS_GITHUB_CLIENT_SECRET=eafc638eaeebe95c0db0fdf59a0a99b9e41832eb -_APP_VCS_GITHUB_WEBHOOK_SECRET=gzbrfuenqodiefbrg39u +_APP_VCS_GITHUB_APP_NAME= +_APP_VCS_GITHUB_PRIVATE_KEY=disabled +_APP_VCS_GITHUB_APP_ID= +_APP_VCS_GITHUB_CLIENT_ID= +_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= \ No newline at end of file From 141b864a56c509f4adb561b98244a8edfdda7fc0 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 14 Nov 2023 19:49:23 +0530 Subject: [PATCH 26/54] Update permissions for create repository document --- app/controllers/api/functions.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 268acc0692..9720a68889 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -761,11 +761,9 @@ App::put('/v1/functions/:functionId') $repository = $dbForConsole->createDocument('repositories', new Document([ '$id' => ID::unique(), '$permissions' => [ - Permission::read(Role::team(ID::custom($teamId))), - Permission::update(Role::team(ID::custom($teamId), 'owner')), - Permission::update(Role::team(ID::custom($teamId), 'developer')), - Permission::delete(Role::team(ID::custom($teamId), 'owner')), - Permission::delete(Role::team(ID::custom($teamId), 'developer')), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), ], 'installationId' => $installation->getId(), 'installationInternalId' => $installation->getInternalId(), From c2e5849c49738b59001d61606dc0a642b9702e0b Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 14 Nov 2023 16:49:45 +0200 Subject: [PATCH 27/54] removing blank line --- src/Appwrite/Usage/Calculators/TimeSeries.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Usage/Calculators/TimeSeries.php b/src/Appwrite/Usage/Calculators/TimeSeries.php index 9ead48e93f..38574b9b03 100644 --- a/src/Appwrite/Usage/Calculators/TimeSeries.php +++ b/src/Appwrite/Usage/Calculators/TimeSeries.php @@ -423,7 +423,6 @@ class TimeSeries extends Calculator */ private function createOrUpdateMetric(string $projectId, string $time, string $period, string $metric, int $value, int $type): void { - $id = \md5("{$time}_{$period}_{$metric}"); $project = $this->database->getDocument('projects', $projectId); $database = call_user_func($this->getProjectDB, $project); From 7e1b618769448fd222d51551f64080d49443c1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 16:50:59 +0100 Subject: [PATCH 28/54] Fix permission issues with repositories collection --- app/controllers/api/functions.php | 18 ++++++++++++------ app/controllers/api/vcs.php | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 9720a68889..cbdbd3a1cb 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -242,12 +242,16 @@ App::post('/v1/functions') // Git connect logic if (!empty($providerRepositoryId)) { + $teamId = $project->getAttribute('teamId', ''); + $repository = $dbForConsole->createDocument('repositories', new Document([ '$id' => ID::unique(), '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), + Permission::read(Role::team(ID::custom($teamId))), + Permission::update(Role::team(ID::custom($teamId), 'owner')), + Permission::update(Role::team(ID::custom($teamId), 'developer')), + Permission::delete(Role::team(ID::custom($teamId), 'owner')), + Permission::delete(Role::team(ID::custom($teamId), 'developer')), ], 'installationId' => $installation->getId(), 'installationInternalId' => $installation->getInternalId(), @@ -761,9 +765,11 @@ App::put('/v1/functions/:functionId') $repository = $dbForConsole->createDocument('repositories', new Document([ '$id' => ID::unique(), '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), + Permission::read(Role::team(ID::custom($teamId))), + Permission::update(Role::team(ID::custom($teamId), 'owner')), + Permission::update(Role::team(ID::custom($teamId), 'developer')), + Permission::delete(Role::team(ID::custom($teamId), 'owner')), + Permission::delete(Role::team(ID::custom($teamId), 'developer')), ], 'installationId' => $installation->getId(), 'installationInternalId' => $installation->getInternalId(), diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 8b61580b76..68a84d0a1d 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -857,10 +857,10 @@ App::post('/v1/vcs/github/events') $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); //find functionId from functions table - $repositories = $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::limit(100), - ]); + ])); // create new deployment only on push and not when branch is created if (!$providerBranchCreated) { @@ -877,13 +877,13 @@ App::post('/v1/vcs/github/events') ]); foreach ($installations as $installation) { - $repositories = $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('installationInternalId', [$installation->getInternalId()]), Query::limit(1000) - ]); + ])); foreach ($repositories as $repository) { - $dbForConsole->deleteDocument('repositories', $repository->getId()); + Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); } $dbForConsole->deleteDocument('installations', $installation->getId()); @@ -915,10 +915,10 @@ App::post('/v1/vcs/github/events') $providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? ''; - $repositories = $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') - ]); + ])); $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request); } elseif ($parsedPayload["action"] == "closed") { @@ -929,10 +929,10 @@ App::post('/v1/vcs/github/events') $external = $parsedPayload["external"] ?? true; if ($external) { - $repositories = $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') - ]); + ])); foreach ($repositories as $repository) { $providerPullRequestIds = $repository->getAttribute('providerPullRequestIds', []); @@ -1092,9 +1092,9 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - $repository = $dbForConsole->getDocument('repositories', $repositoryId, [ + $repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ Query::equal('projectInternalId', [$project->getInternalId()]) - ]); + ])); if ($repository->isEmpty()) { throw new Exception(Exception::REPOSITORY_NOT_FOUND); @@ -1109,7 +1109,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor // TODO: Delete from array when PR is closed - $repository = $dbForConsole->updateDocument('repositories', $repository->getId(), $repository); + $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); $privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID'); From 1870524be5748709bf2b2d2a80f7f224fc2f1069 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 14 Nov 2023 21:34:17 +0530 Subject: [PATCH 29/54] fixes error name --- app/controllers/api/messaging.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index f8d0ae16ec..dd5ab2bd1a 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -812,10 +812,10 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); @@ -906,7 +906,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -987,7 +987,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -1074,7 +1074,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -1248,7 +1248,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -1335,7 +1335,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -1414,7 +1414,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } @@ -1490,7 +1490,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') if ($enabled === true || $enabled === false) { if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_DISABLED); + throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } $provider->setAttribute('enabled', $enabled); } From 1c4fea0fc3e77b80cecad66a556ba9856d9d9076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 14 Nov 2023 17:45:02 +0100 Subject: [PATCH 30/54] Fix patch script, make errors silent --- .../patchRecreateRepositoriesDocuments.php | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php index 4d04802f50..93e6c527bb 100644 --- a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php +++ b/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php @@ -37,9 +37,17 @@ class PatchRecreateRepositoriesDocuments extends Action $startTime = microtime(true); if (!empty($projectId)) { - $project = $dbForConsole->getDocument('projects', $projectId); - $dbForProject = call_user_func($getProjectDB, $project); - $this->recreateRepositories($dbForConsole, $dbForProject, $project); + try { + $project = $dbForConsole->getDocument('projects', $projectId); + $dbForProject = call_user_func($getProjectDB, $project); + $this->recreateRepositories($dbForConsole, $dbForProject, $project); + } catch (\Throwable $th) { + Console::error("Unexpected error occured with Project ID {$projectId}"); + Console::error('[Error] Type: ' . get_class($th)); + Console::error('[Error] Message: ' . $th->getMessage()); + Console::error('[Error] File: ' . $th->getFile()); + Console::error('[Error] Line: ' . $th->getLine()); + } } else { $queries = []; if (!empty($after)) { @@ -50,8 +58,18 @@ class PatchRecreateRepositoriesDocuments extends Action Console::info("Iterating all projects"); } $this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole) { - $dbForProject = call_user_func($getProjectDB, $project); - $this->recreateRepositories($dbForConsole, $dbForProject, $project); + $projectId = $project->getId(); + + try { + $dbForProject = call_user_func($getProjectDB, $project); + $this->recreateRepositories($dbForConsole, $dbForProject, $project); + } catch (\Throwable $th) { + Console::error("Unexpected error occured with Project ID {$projectId}"); + Console::error('[Error] Type: ' . get_class($th)); + Console::error('[Error] Message: ' . $th->getMessage()); + Console::error('[Error] File: ' . $th->getFile()); + Console::error('[Error] Line: ' . $th->getLine()); + } }); } From adc76c5797c2b456743edffc909feb3ac9405b26 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 14 Nov 2023 22:46:20 +0530 Subject: [PATCH 31/54] adds target when account is created or email or phone is updated --- app/config/collections.php | 4 +-- app/controllers/api/account.php | 54 +++++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index bfa8192b74..5571fd57d6 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1926,9 +1926,9 @@ $commonCollections = [ 'orders' => [], ], [ - '$id' => ID::custom('_key_identifier_providerId'), + '$id' => ID::custom('_key_identifier_userInternalId'), 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['providerId', 'identifier'], + 'attributes' => ['identifier', 'userInternalId'], 'lengths' => [], 'orders' => [], ] diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 77ff2a27e4..8a15432a25 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -147,8 +147,15 @@ App::post('/v1/account') 'search' => implode(' ', [$userId, $email, $name]), 'accessedAt' => DateTime::now(), ]); + $userInternalId = $user->getInternalId(); $user->removeAttribute('$internalId'); Authorization::skip(fn() => $dbForProject->createDocument('users', $user)); + Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $userInternalId, + 'providerType' => 'email', + 'identifier' => $email, + ]))); } catch (Duplicate) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -1329,10 +1336,10 @@ App::post('/v1/account/sessions/phone') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), - Query::equal('providerInternalId', [$provider->getInternalId()]) + Query::equal('userInternalId', [$user->getInternalId()]) ]); - if (!$target) { + if (!$target || $target->isEmpty()) { $target = $dbForProject->createDocument('targets', new Document([ 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), @@ -1995,6 +2002,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 @@ -2019,8 +2027,23 @@ App::patch('/v1/account/email') ->setAttribute('passwordUpdate', DateTime::now()); } + $target = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + Query::equal('userInternalId', [$user->getInternalId()]) + ]); + + if ($target && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); } catch (Duplicate) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2065,6 +2088,20 @@ App::patch('/v1/account/phone') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } + $target = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$phone]), + Query::equal('userInternalId', [$user->getInternalId()]) + ]); + + if ($target && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $user->getAttribute('phone'), 'targets'); + $user ->setAttribute('phone', $phone) ->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again @@ -2080,6 +2117,7 @@ App::patch('/v1/account/phone') try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } @@ -2904,6 +2942,7 @@ App::post('/v1/account/verification/phone') Query::equal('internal', [true]), Query::equal('type', ['sms']) ])); + if ($provider === false || $provider->isEmpty()) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -2952,18 +2991,9 @@ App::post('/v1/account/verification/phone') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$user->getAttribute('phone')]), - Query::equal('providerInternalId', [$provider->getInternalId()]) + Query::equal('userInternalId', [$user->getInternalId()]) ]); - if (!$target) { - $target = $dbForProject->createDocument('targets', new Document([ - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $user->getAttribute('phone'), - ])); - } - $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $verification->getId(), 'targets' => [$target->getId()], From fdca55b0de9d61f72dc8822ce1d9148de464d851 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Wed, 15 Nov 2023 01:24:55 +0530 Subject: [PATCH 32/54] fix test cases --- app/config/collections.php | 9 +- app/controllers/api/account.php | 70 +++- tests/e2e/Services/GraphQL/MessagingTest.php | 318 +----------------- .../e2e/Services/Messaging/MessagingBase.php | 247 +------------- 4 files changed, 66 insertions(+), 578 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 5571fd57d6..f2ee405c92 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1920,18 +1920,11 @@ $commonCollections = [ ], [ '$id' => ID::custom('_key_identifier'), - 'type' => Database::INDEX_KEY, + 'type' => Database::INDEX_UNIQUE, 'attributes' => ['identifier'], 'lengths' => [], 'orders' => [], ], - [ - '$id' => ID::custom('_key_identifier_userInternalId'), - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['identifier', 'userInternalId'], - 'lengths' => [], - 'orders' => [], - ] ], ], ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8a15432a25..c5e389dd42 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -147,15 +147,21 @@ App::post('/v1/account') 'search' => implode(' ', [$userId, $email, $name]), 'accessedAt' => DateTime::now(), ]); - $userInternalId = $user->getInternalId(); $user->removeAttribute('$internalId'); - Authorization::skip(fn() => $dbForProject->createDocument('users', $user)); - Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ + $user = Authorization::skip(fn() => $dbForProject->createDocument('users', $user)); + $target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($userId)), + Permission::delete(Role::user($userId)), + ], 'userId' => $user->getId(), - 'userInternalId' => $userInternalId, + 'userInternalId' => $user->getInternalId(), 'providerType' => 'email', 'identifier' => $email, ]))); + $user->setAttribute('targets', [$target]); + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -663,7 +669,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' => 'email', + 'identifier' => $email, + ])); } catch (Duplicate) { $failureRedirect(Exception::USER_ALREADY_EXISTS); } @@ -1336,16 +1353,21 @@ App::post('/v1/account/sessions/phone') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), - Query::equal('userInternalId', [$user->getInternalId()]) ]); if (!$target || $target->isEmpty()) { $target = $dbForProject->createDocument('targets', new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'providerType' => 'sms', 'identifier' => $phone, ])); + $dbForProject->deleteCachedDocument('users', $user->getId()); } $messageDoc = $dbForProject->createDocument('messages', new Document([ @@ -2029,7 +2051,6 @@ App::patch('/v1/account/email') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), - Query::equal('userInternalId', [$user->getInternalId()]) ]); if ($target && !$target->isEmpty()) { @@ -2041,9 +2062,16 @@ App::patch('/v1/account/email') */ $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + if ($oldTarget !== false && !$oldTarget->isEmpty()) { + try { + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); + } catch (Duplicate) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + } + try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); } catch (Duplicate) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2090,7 +2118,6 @@ App::patch('/v1/account/phone') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), - Query::equal('userInternalId', [$user->getInternalId()]) ]); if ($target && !$target->isEmpty()) { @@ -2115,9 +2142,16 @@ App::patch('/v1/account/phone') ->setAttribute('passwordUpdate', DateTime::now()); } + if ($oldTarget !== false && !$oldTarget->isEmpty()) { + try { + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); + } catch (Duplicate) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + } + try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } @@ -2991,9 +3025,23 @@ App::post('/v1/account/verification/phone') $target = $dbForProject->findOne('targets', [ Query::equal('identifier', [$user->getAttribute('phone')]), - Query::equal('userInternalId', [$user->getInternalId()]) ]); + if (!$target || $target->isEmpty()) { + $target = $dbForProject->createDocument('targets', new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => 'sms', + 'identifier' => $user->getAttribute('phone'), + ])); + $dbForProject->deleteCachedDocument('users', $user->getId()); + } + $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $verification->getId(), 'targets' => [$target->getId()], diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 57ed825a5c..112e9e5633 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -680,120 +680,13 @@ class MessagingTest extends Scope */ public function testUpdateEmail(array $email) { - if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { - $this->markTestSkipped('Email DSN not provided'); - } - - $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); - $to = $emailDSN->getParam('to'); - $from = $emailDSN->getParam('from'); - $isEuRegion = $emailDSN->getParam('isEuRegion'); - $apiKey = $emailDSN->getPassword(); - $domain = $emailDSN->getUser(); - - if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { - $this->markTestSkipped('Email provider not configured'); - } - - $query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'Mailgun2', - 'apiKey' => $apiKey, - 'domain' => $domain, - 'from' => $from, - 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), - ], - ]; - $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $provider['headers']['status-code']); - - $providerId = $provider['body']['data']['messagingCreateMailgunProvider']['_id']; - - $query = $this->getQuery(self::$CREATE_TOPIC); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Active users', - ], - ]; - $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $topic['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'userId' => ID::unique(), - 'email' => 'random2-mail@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ] - ]; - $user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $user['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER_TARGET); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'targetId' => ID::unique(), - 'providerType' => 'email', - 'userId' => $user['body']['data']['usersCreate']['_id'], - 'providerId' => $providerId, - 'identifier' => $to, - ], - ]; - $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $target['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_SUBSCRIBER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'subscriberId' => ID::unique(), - 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], - 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], - ], - ]; - $subscriber = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), $graphQLPayload); - - $this->assertEquals(200, $subscriber['headers']['status-code']); - $query = $this->getQuery(self::$CREATE_EMAIL); $graphQLPayload = [ 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$email['topics'][0]], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ], @@ -992,118 +885,13 @@ class MessagingTest extends Scope */ public function testUpdateSMS(array $sms) { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } - - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { - $this->markTestSkipped('SMS provider not configured'); - } - - $query = $this->getQuery(self::$CREATE_MSG91_PROVIDER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'Msg91-2', - 'senderId' => $senderId, - 'authKey' => $authKey, - 'from' => $from, - ], - ]; - $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $provider['headers']['status-code']); - - $providerId = $provider['body']['data']['messagingCreateMsg91Provider']['_id']; - - $query = $this->getQuery(self::$CREATE_TOPIC); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Active users', - ], - ]; - $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $topic['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'userId' => ID::unique(), - 'email' => 'random4-email@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ] - ]; - $user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $user['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER_TARGET); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'targetId' => ID::unique(), - 'providerType' => 'sms', - 'userId' => $user['body']['data']['usersCreate']['_id'], - 'providerId' => $providerId, - 'identifier' => $to, - ], - ]; - $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $target['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_SUBSCRIBER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'subscriberId' => ID::unique(), - 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], - 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], - ], - ]; - $subscriber = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), $graphQLPayload); - - $this->assertEquals(200, $subscriber['headers']['status-code']); - $query = $this->getQuery(self::$CREATE_SMS); $graphQLPayload = [ 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$sms['topics'][0]], 'content' => '345463', ], ]; @@ -1299,113 +1087,13 @@ class MessagingTest extends Scope */ public function testUpdatePushNotification(array $push) { - if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { - $this->markTestSkipped('Push DSN empty'); - } - - $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); - $to = $pushDSN->getParam('to'); - $serverKey = $pushDSN->getPassword(); - - if (empty($to) || empty($serverKey)) { - $this->markTestSkipped('Push provider not configured'); - } - - $query = $this->getQuery(self::$CREATE_FCM_PROVIDER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'FCM2', - 'serverKey' => $serverKey, - ], - ]; - $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $provider['headers']['status-code']); - $providerId = $provider['body']['data']['messagingCreateFcmProvider']['_id']; - - $query = $this->getQuery(self::$CREATE_TOPIC); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Active users', - ], - ]; - $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $topic['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'userId' => ID::unique(), - 'email' => 'random5-email@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ] - ]; - $user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $user['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_USER_TARGET); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'targetId' => ID::unique(), - 'providerType' => 'push', - 'userId' => $user['body']['data']['usersCreate']['_id'], - 'providerId' => $providerId, - 'identifier' => $to, - ], - ]; - $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), $graphQLPayload); - - $this->assertEquals(200, $target['headers']['status-code']); - - $query = $this->getQuery(self::$CREATE_SUBSCRIBER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'subscriberId' => ID::unique(), - 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], - 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], - ], - ]; - $subscriber = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), $graphQLPayload); - - $this->assertEquals(200, $subscriber['headers']['status-code']); - $query = $this->getQuery(self::$CREATE_PUSH_NOTIFICATION); $graphQLPayload = [ 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$push['topics'][0]], 'title' => 'Push Notification Title', 'body' => 'Push Notifiaction Body', ], diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 707ba2f7b5..daafd52e8f 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -649,21 +649,6 @@ trait MessagingBase */ public function testUpdateEmail(array $email) { - if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { - $this->markTestSkipped('Email DSN not provided'); - } - - $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); - $to = $emailDSN->getParam('to'); - $from = $emailDSN->getParam('from'); - $isEuRegion = $emailDSN->getParam('isEuRegion'); - $apiKey = $emailDSN->getPassword(); - $domain = $emailDSN->getUser(); - - if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { - $this->markTestSkipped('Email provider not configured'); - } - $message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -673,74 +658,6 @@ trait MessagingBase // Test failure as the message has already been sent. $this->assertEquals(400, $message['headers']['status-code']); - // Create provider - $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/mailgun', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'providerId' => ID::unique(), - 'name' => 'Mailgun-provider-2', - 'apiKey' => $apiKey, - 'domain' => $domain, - 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), - 'from' => $from - ]); - - $this->assertEquals(201, $provider['headers']['status-code']); - - // Create Topic - $topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Test Topic' - ]); - - $this->assertEquals(201, $topic['headers']['status-code']); - - // Create User - $user = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'userId' => ID::unique(), - 'email' => 'random-email@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ]); - - $this->assertEquals(201, $user['headers']['status-code']); - - // Create Target - $target = $this->client->call(Client::METHOD_POST, '/users/' . $user['body']['$id'] . '/targets', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'targetId' => ID::unique(), - 'providerType' => 'email', - 'providerId' => $provider['body']['$id'], - 'identifier' => $to, - ]); - - $this->assertEquals(201, $target['headers']['status-code']); - - // Create Subscriber - $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'subscriberId' => ID::unique(), - 'targetId' => $target['body']['$id'], - ]); - - $this->assertEquals(201, $subscriber['headers']['status-code']); - // Create Email $email = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [ 'content-type' => 'application/json', @@ -749,7 +666,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['$id']], + 'topics' => [$email['body']['topics'][0]], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ]); @@ -897,20 +814,6 @@ trait MessagingBase */ public function testUpdateSMS(array $sms) { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } - - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { - $this->markTestSkipped('SMS provider not configured'); - } - $message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/sms/' . $sms['body']['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -920,73 +823,6 @@ trait MessagingBase // Test failure as the message has already been sent. $this->assertEquals(400, $message['headers']['status-code']); - // Create provider - $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/msg91', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'providerId' => ID::unique(), - 'name' => 'Msg91-2', - 'senderId' => $senderId, - 'authKey' => $authKey, - 'from' => $from - ]); - - $this->assertEquals(201, $provider['headers']['status-code']); - - // Create Topic - $topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Test Topic' - ]); - - $this->assertEquals(201, $topic['headers']['status-code']); - - // Create User - $user = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'userId' => ID::unique(), - 'email' => 'random2-email@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ]); - - $this->assertEquals(201, $user['headers']['status-code']); - - // Create Target - $target = $this->client->call(Client::METHOD_POST, '/users/' . $user['body']['$id'] . '/targets', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'targetId' => ID::unique(), - 'providerType' => 'sms', - 'providerId' => $provider['body']['$id'], - 'identifier' => $to, - ]); - - $this->assertEquals(201, $target['headers']['status-code']); - - // Create Subscriber - $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'subscriberId' => ID::unique(), - 'targetId' => $target['body']['$id'], - ]); - - $this->assertEquals(201, $subscriber['headers']['status-code']); - // Create SMS $sms = $this->client->call(Client::METHOD_POST, '/messaging/messages/sms', [ 'content-type' => 'application/json', @@ -995,7 +831,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['$id']], + 'topics' => [$sms['body']['topics'][0]], 'content' => '047487', ]); @@ -1139,18 +975,6 @@ trait MessagingBase */ public function testUpdatePushNotification(array $push) { - if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { - $this->markTestSkipped('Push DSN empty'); - } - - $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); - $to = $pushDSN->getParam('to'); - $serverKey = $pushDSN->getPassword(); - - if (empty($to) || empty($serverKey)) { - $this->markTestSkipped('Push provider not configured'); - } - $message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/push/' . $push['body']['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1160,71 +984,6 @@ trait MessagingBase // Test failure as the message has already been sent. $this->assertEquals(400, $message['headers']['status-code']); - // Create provider - $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/fcm', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'providerId' => ID::unique(), - 'name' => 'FCM-2', - 'serverKey' => $serverKey, - ]); - - $this->assertEquals(201, $provider['headers']['status-code']); - - // Create Topic - $topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Test Topic' - ]); - - $this->assertEquals(201, $topic['headers']['status-code']); - - // Create User - $user = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'userId' => ID::unique(), - 'email' => 'random4-email@mail.org', - 'password' => 'password', - 'name' => 'Messaging User', - ]); - - $this->assertEquals(201, $user['headers']['status-code']); - - // Create Target - $target = $this->client->call(Client::METHOD_POST, '/users/' . $user['body']['$id'] . '/targets', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'targetId' => ID::unique(), - 'providerType' => 'push', - 'providerId' => $provider['body']['$id'], - 'identifier' => $to, - ]); - - $this->assertEquals(201, $target['headers']['status-code']); - - // Create Subscriber - $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'subscriberId' => ID::unique(), - 'targetId' => $target['body']['$id'], - ]); - - $this->assertEquals(201, $subscriber['headers']['status-code']); - // Create push notification $push = $this->client->call(Client::METHOD_POST, '/messaging/messages/push', [ 'content-type' => 'application/json', @@ -1233,7 +992,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'topics' => [$topic['body']['$id']], + 'topics' => [$push['body']['topics'][0]], 'title' => 'Test-Notification', 'body' => 'Test-Notification-Body', ]); From c3015340ce7ae0d3686ce8aa5a11b36b4226c5f7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 15 Nov 2023 16:46:43 +1300 Subject: [PATCH 33/54] Update database --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 25f0c3964d..d362f3aa9c 100644 --- a/composer.lock +++ b/composer.lock @@ -1906,16 +1906,16 @@ }, { "name": "utopia-php/database", - "version": "0.45.1", + "version": "0.45.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0e76f996439b80794ab73c2fffdb51ebd6676e4b" + "reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0e76f996439b80794ab73c2fffdb51ebd6676e4b", - "reference": "0e76f996439b80794ab73c2fffdb51ebd6676e4b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/dc789f2c1fd8b5ee07ff883e11c9ad7970824788", + "reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788", "shasum": "" }, "require": { @@ -1956,9 +1956,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.45.1" + "source": "https://github.com/utopia-php/database/tree/0.45.2" }, - "time": "2023-11-01T08:30:19+00:00" + "time": "2023-11-15T03:38:47+00:00" }, { "name": "utopia-php/domains", From 94931ff4ce5d8bfb1057c4743db488d2566874a3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 15 Nov 2023 16:47:49 +1300 Subject: [PATCH 34/54] Fix non-PSR-compliant file name --- ...toriesDocuments.php => PatchRecreateRepositoriesDocuments.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Appwrite/Platform/Tasks/{patchRecreateRepositoriesDocuments.php => PatchRecreateRepositoriesDocuments.php} (100%) diff --git a/src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php b/src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php similarity index 100% rename from src/Appwrite/Platform/Tasks/patchRecreateRepositoriesDocuments.php rename to src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php From 2e70ed59af5efcccbf3bf55e14176310e240e5e0 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:41:42 +0530 Subject: [PATCH 35/54] Fix git installation deletion --- app/controllers/api/vcs.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 68a84d0a1d..1b0c993e11 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -1046,8 +1046,8 @@ App::delete('/v1/vcs/installations/:installationId') ->inject('response') ->inject('project') ->inject('dbForConsole') - ->inject('deletes') - ->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) { + ->inject('queueForDeletes') + ->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) { $installation = $dbForConsole->getDocument('installations', $installationId); if ($installation->isEmpty()) { @@ -1058,7 +1058,7 @@ App::delete('/v1/vcs/installations/:installationId') throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB'); } - $deletes + $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($installation); From 2aa8391c5e2bc37228ba016a5924b32ed9017efb Mon Sep 17 00:00:00 2001 From: prateek banga Date: Wed, 15 Nov 2023 12:52:27 +0530 Subject: [PATCH 36/54] review fixes --- app/controllers/api/account.php | 12 ++---------- app/controllers/api/messaging.php | 4 ---- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c5e389dd42..1d6780f48b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2063,11 +2063,7 @@ App::patch('/v1/account/email') $oldTarget = $user->find('identifier', $oldEmail, 'targets'); if ($oldTarget !== false && !$oldTarget->isEmpty()) { - try { - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); - } catch (Duplicate) { - throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); - } + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); } try { @@ -2143,11 +2139,7 @@ App::patch('/v1/account/phone') } if ($oldTarget !== false && !$oldTarget->isEmpty()) { - try { - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); - } catch (Duplicate) { - throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); - } + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); } try { diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index dd5ab2bd1a..1f4f7b62b6 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -814,10 +814,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') if ($provider->getAttribute('internal') === true && $enabled === false) { throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); } - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } - $provider->setAttribute('enabled', $enabled); } From 7efecbca8b0637d0a16c7d78ded922d43a44a5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Wed, 15 Nov 2023 17:20:50 +0000 Subject: [PATCH 37/54] chore: fixed indentation --- src/Appwrite/Usage/Calculators/TimeSeries.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Usage/Calculators/TimeSeries.php b/src/Appwrite/Usage/Calculators/TimeSeries.php index 38574b9b03..6dbf49c44c 100644 --- a/src/Appwrite/Usage/Calculators/TimeSeries.php +++ b/src/Appwrite/Usage/Calculators/TimeSeries.php @@ -432,13 +432,13 @@ class TimeSeries extends Calculator $document = $database->getDocument('stats', $id); if ($document->isEmpty()) { $database->createDocument('stats', new Document([ - '$id' => $id, - 'period' => $period, - 'time' => $time, - 'metric' => $metric, - 'value' => $value, - 'type' => $type, - 'region' => $this->region, + '$id' => $id, + 'period' => $period, + 'time' => $time, + 'metric' => $metric, + 'value' => $value, + 'type' => $type, + 'region' => $this->region, ])); } else { $database->updateDocument( From 31d5b75034e95c196666e9516f9e2eb01ac97d9a Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Wed, 15 Nov 2023 17:23:35 +0000 Subject: [PATCH 38/54] Bump console to version 3.2.7 --- .gitmodules | 2 +- app/console | 2 +- app/init.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index ff2e0a6aab..af12124355 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.6 + branch = 3.2.7 diff --git a/app/console b/app/console index f7c34a1b37..49d039ed07 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit f7c34a1b37d53dd5f28c83b4e12a4e68fcd9b484 +Subproject commit 49d039ed07628155e7f56e2c997fcef90ecde267 diff --git a/app/init.php b/app/init.php index be6b440498..2572d01151 100644 --- a/app/init.php +++ b/app/init.php @@ -109,7 +109,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours -const APP_CACHE_BUSTER = 516; +const APP_CACHE_BUSTER = 327; const APP_VERSION_STABLE = '1.4.11'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; From 6b5e734dfaedfb6fb291d7dab6a7a883dcc5a555 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 15 Nov 2023 20:09:50 +0200 Subject: [PATCH 39/54] indentation small fix --- src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index ee9931bde4..5f0ecbe1db 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -112,9 +112,9 @@ class DeleteOrphanedProjects extends Action $dbForConsole->deleteCachedDocument('projects', $project->getId()); } - Console::info('--Deleting project no (' . $project->getInternalId() . ')'); + Console::info('--Deleting project no (' . $project->getInternalId() . ')'); - $orphans++; + $orphans++; } catch (\Throwable $th) { Console::error('Error: ' . $th->getMessage()); } finally { From 37e1e251705f11f7f192bbc2002b343a226508aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Wed, 15 Nov 2023 18:31:33 +0000 Subject: [PATCH 40/54] chore: update versions and changelog --- CHANGES.md | 4 ++++ README-CN.md | 6 +++--- README.md | 6 +++--- app/init.php | 2 +- src/Appwrite/Migration/Migration.php | 1 + 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e8f0c08c1f..513ee2ec90 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# Version 1.4.12 + +## Bug fixes + # Version 1.4.11 ## Miscellaneous diff --git a/README-CN.md b/README-CN.md index 4e45c423c4..1bbc8eb234 100644 --- a/README-CN.md +++ b/README-CN.md @@ -66,7 +66,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` ### Windows @@ -78,7 +78,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` #### PowerShell @@ -88,7 +88,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index 88615a355a..3a1d2dbe9f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` ### Windows @@ -88,7 +88,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` #### PowerShell @@ -98,7 +98,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.4.11 + appwrite/appwrite:1.4.12 ``` Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation. diff --git a/app/init.php b/app/init.php index 2572d01151..2c0219eec2 100644 --- a/app/init.php +++ b/app/init.php @@ -110,7 +110,7 @@ const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours const APP_CACHE_BUSTER = 327; -const APP_VERSION_STABLE = '1.4.11'; +const APP_VERSION_STABLE = '1.4.12'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 406acae7df..8f68e31be4 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -76,6 +76,7 @@ abstract class Migration '1.4.9' => 'V19', '1.4.10' => 'V19', '1.4.11' => 'V19', + '1.4.12' => 'V19' ]; /** From e145ad4cb4ed9c0bc6e6c8613bf51c17e5b5ba5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Wed, 15 Nov 2023 18:41:57 +0000 Subject: [PATCH 41/54] chore: update versions and changelog --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 513ee2ec90..889f65e1e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,16 @@ # Version 1.4.12 +## Miscellaneous +* Bump console to version 3.2.7 [#7148](https://github.com/appwrite/appwrite/pull/7148) +* Chore update database to 0.45.2 [#7138](https://github.com/appwrite/appwrite/pull/7138) +* Implement queue thresholds for the health API [#7123](https://github.com/appwrite/appwrite/pull/7123) +* Add Authorization::skip to the usage worker [#7124](https://github.com/appwrite/appwrite/pull/7124) + ## Bug fixes +* fix: use queueForDeletes in git installation delete endpoint [#7140](https://github.com/appwrite/appwrite/pull/7140) +* fix: patch script, make errors silent [#7134](https://github.com/appwrite/appwrite/pull/7134) +* fix: repositories recreation script [#7133](https://github.com/appwrite/appwrite/pull/7133) +* fix: Only delete repositories linked to the particular project [#7131](https://github.com/appwrite/appwrite/pull/7131) # Version 1.4.11 From c24664f5d93042e3b3b08ed4fee74dfcca7ef5d4 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 01:30:47 +0530 Subject: [PATCH 42/54] removes internal provider --- app/config/collections.php | 22 +- app/config/errors.php | 5 - app/config/variables.php | 2 +- app/controllers/api/account.php | 69 +---- app/controllers/api/messaging.php | 261 +----------------- app/controllers/api/teams.php | 29 +- docker-compose.yml | 2 + src/Appwrite/Event/Messaging.php | 80 +++++- src/Appwrite/Platform/Workers/Messaging.php | 87 +++++- .../Database/Validator/Queries/Providers.php | 1 - .../Utopia/Response/Model/Provider.php | 6 - .../Account/AccountCustomClientTest.php | 67 +---- tests/e2e/Services/GraphQL/AccountTest.php | 31 --- tests/e2e/Services/GraphQL/Base.php | 21 +- 14 files changed, 209 insertions(+), 474 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index f2ee405c92..0d42dfe895 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1416,17 +1416,6 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('internal'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => false, - 'default' => false, - 'array' => false, - ], [ '$id' => ID::custom('enabled'), 'type' => Database::VAR_BOOLEAN, @@ -1495,16 +1484,9 @@ $commonCollections = [ 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_internal'), + '$id' => ID::custom('_key_enabled_type'), 'type' => Database::INDEX_KEY, - 'attributes' => ['internal'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_internal_type'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['internal','type'], + 'attributes' => ['enabled','type'], 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], diff --git a/app/config/errors.php b/app/config/errors.php index e915091940..1117f20e48 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -781,11 +781,6 @@ return [ 'description' => 'Provider with the requested ID is of incorrect type: ', 'code' => 400, ], - Exception::PROVIDER_INTERNAL_UPDATE_DISABLED => [ - 'name' => Exception::PROVIDER_INTERNAL_UPDATE_DISABLED, - 'description' => 'Provider with the requested ID cannot be disabled.', - 'code' => 400, - ], /** Topic Errors */ Exception::TOPIC_NOT_FOUND => [ diff --git a/app/config/variables.php b/app/config/variables.php index 9d555bf013..6f49d4a5da 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -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, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 1d6780f48b..6486147a84 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1260,12 +1260,7 @@ App::post('/v1/account/sessions/phone') ->inject('queueForMessaging') ->inject('locale') ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -1351,35 +1346,18 @@ App::post('/v1/account/sessions/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->findOne('targets', [ - Query::equal('identifier', [$phone]), - ]); - if (!$target || $target->isEmpty()) { - $target = $dbForProject->createDocument('targets', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $phone, - ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); - } - - $messageDoc = $dbForProject->createDocument('messages', new Document([ + $messageDoc = new Document([ '$id' => $token->getId(), - 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$phone]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); @@ -2964,12 +2942,7 @@ App::post('/v1/account/verification/phone') ->inject('project') ->inject('locale') ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3015,35 +2988,17 @@ App::post('/v1/account/verification/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->findOne('targets', [ - Query::equal('identifier', [$user->getAttribute('phone')]), - ]); - - if (!$target || $target->isEmpty()) { - $target = $dbForProject->createDocument('targets', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $user->getAttribute('phone'), - ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); - } - - $messageDoc = $dbForProject->createDocument('messages', new Document([ + $messageDoc = new Document([ '$id' => $verification->getId(), - 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$user->getAttribute('phone')]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 1f4f7b62b6..e1e531c1a1 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -79,16 +79,6 @@ App::post('/v1/messaging/providers/mailgun') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['email']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -141,16 +131,6 @@ App::post('/v1/messaging/providers/sendgrid') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -205,16 +185,6 @@ App::post('/v1/messaging/providers/msg91') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -269,16 +239,6 @@ App::post('/v1/messaging/providers/telesign') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -333,16 +293,6 @@ App::post('/v1/messaging/providers/textmagic') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -397,16 +347,6 @@ App::post('/v1/messaging/providers/twilio') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -461,16 +401,6 @@ App::post('/v1/messaging/providers/vonage') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -519,16 +449,6 @@ App::post('/v1/messaging/providers/fcm') ], ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['push']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -585,16 +505,6 @@ App::post('/v1/messaging/providers/apns') ], ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['push']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -776,7 +686,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) @@ -784,7 +693,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -806,14 +715,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -835,15 +737,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -868,13 +761,12 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -896,14 +788,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -915,15 +800,6 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -948,14 +824,13 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('senderId', '', new Text(0), 'Msg91 Sender ID.', true) ->param('authKey', '', new Text(0), 'Msg91 Auth Key.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $senderId, string $authKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $senderId, string $authKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -977,14 +852,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1002,15 +870,6 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1035,14 +894,13 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Telesign username.', true) ->param('password', '', new Text(0), 'Telesign password.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $password, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $password, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1064,14 +922,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1089,15 +940,6 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1122,14 +964,13 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Textmagic username.', true) ->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1151,14 +992,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1176,15 +1010,6 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1209,14 +1034,13 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('accountSid', null, new Text(0), 'Twilio account secret ID.', true) ->param('authToken', null, new Text(0), 'Twilio authentication token.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $accountSid, string $authToken, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $accountSid, string $authToken, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1238,14 +1062,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1263,15 +1080,6 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1296,14 +1104,13 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Vonage API key.', true) ->param('apiSecret', '', new Text(0), 'Vonage API secret.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $apiSecret, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $apiSecret, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1325,14 +1132,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1350,15 +1150,6 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1383,12 +1174,11 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('serverKey', '', new Text(0), 'FCM Server Key.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1404,14 +1194,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider->setAttribute('name', $name); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1421,15 +1204,6 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1455,7 +1229,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('authKey', '', new Text(0), 'APNS authentication key.', true) ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) ->param('teamId', '', new Text(0), 'APNS team ID.', true) @@ -1464,7 +1237,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1480,14 +1253,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('name', $name); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1517,15 +1283,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index b19080e894..2ba27efcb3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -628,12 +628,7 @@ App::post('/v1/teams/:teamId/memberships') ->trigger() ; } elseif (!empty($phone)) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -647,27 +642,17 @@ App::post('/v1/teams/:teamId/memberships') $message = $message->setParam('{{token}}', $url); $message = $message->render(); - $target = $dbForProject->createDocument('targets', new Document([ - 'userId' => $invitee->getId(), - 'userInternalId' => $invitee->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $phone, - ])); - - $messageDoc = $dbForProject->createDocument('messages', new Document([ - // Here membership ID is used as message ID so that it can be used in test cases to verify the message - '$id' => $membership->getId(), - 'targets' => [$target->getId()], + $messageDoc = new Document([ + '$id' => ID::unique(), 'data' => [ 'content' => $message, ], - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), - 'deliveryTime' => Datetime::now(), - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$phone]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); } diff --git a/docker-compose.yml b/docker-compose.yml index 8255508356..a570c5b619 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -582,6 +582,8 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_SMS_FROM + - _APP_SMS_PROVIDER appwrite-worker-migrations: entrypoint: worker-migrations diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 76abecd1c0..354418486b 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -9,7 +9,11 @@ use Utopia\Queue\Client; class Messaging extends Event { protected ?string $messageId = null; - private ?string $deliveryTime = null; + protected ?Document $message = null; + protected ?array $recipients = null; + protected ?string $deliveryTime = null; + protected ?string $providerType = null; + public function __construct(protected Connection $connection) { @@ -20,6 +24,52 @@ class Messaging extends Event ->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. * @@ -43,6 +93,29 @@ class Messaging extends Event 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 Delivery time for the messaging event. * @@ -92,6 +165,9 @@ class Messaging extends Event 'project' => $this->project, 'user' => $this->user, 'messageId' => $this->messageId, + 'message' => $this->message, + 'recipients' => $this->recipients, + 'providerType' => $this->providerType, ]); } -} +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 68bae1b4d0..b2b0094a88 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -3,7 +3,10 @@ namespace Appwrite\Platform\Workers; use Appwrite\Extend\Exception; +use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Helpers\ID; +use Utopia\DSN\DSN; use Utopia\Platform\Action; use Utopia\Queue\Message; use Utopia\Database\Database; @@ -63,11 +66,19 @@ class Messaging extends Action return; } - $message = $dbForProject->getDocument('messages', $payload['messageId']); + if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) { + if ($payload['providerType'] === 'SMS') { + $this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']); + } + } else { + $message = $dbForProject->getDocument('messages', $payload['messageId']); - $this->processMessage($dbForProject, $message); + $this->processMessage($dbForProject, $message); + } } + + private function processMessage(Database $dbForProject, Document $message): void { $topicsId = $message->getAttribute('topics', []); @@ -99,7 +110,7 @@ class Messaging extends Action } $internalProvider = $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), + Query::equal('enabled', [true]), Query::equal('type', [$recipients[0]->getAttribute('providerType')]), ]); @@ -215,6 +226,74 @@ class Messaging extends Action $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; + } + + $smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER')); + $host = $smsDSN->getHost(); + $password = $smsDSN->getPassword(); + $user = $smsDSN->getUser(); + + $from = App::getEnv('_APP_SMS_FROM'); + + $provider = new Document([ + '$id' => ID::unique(), + 'provider' => $host, + '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 + ] + ]); + $adapter = $this->sms($provider); + + $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 { } @@ -225,7 +304,7 @@ class Messaging extends Action return match ($provider->getAttribute('provider')) { 'mock' => new Mock('username', 'password'), 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']), - 'text-magic' => new TextMagic($credentials['username'], $credentials['apiKey']), + 'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']), 'telesign' => new Telesign($credentials['username'], $credentials['password']), 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']), 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']), diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php index 7760acbdad..a28f5e8437 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php @@ -8,7 +8,6 @@ class Providers extends Base 'name', 'provider', 'type', - 'internal', 'enabled', ]; diff --git a/src/Appwrite/Utopia/Response/Model/Provider.php b/src/Appwrite/Utopia/Response/Model/Provider.php index a121753275..f8a0514020 100644 --- a/src/Appwrite/Utopia/Response/Model/Provider.php +++ b/src/Appwrite/Utopia/Response/Model/Provider.php @@ -40,12 +40,6 @@ class Provider extends Model 'default' => '', 'example' => 'mailgun', ]) - ->addRule('internal', [ - 'type' => self::TYPE_BOOLEAN, - 'description' => 'Is this a pre-configured provider instance?', - 'default' => false, - 'example' => true, - ]) ->addRule('enabled', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is provider enabled?', diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index d56f93a42c..1cc9e6c325 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -744,33 +744,8 @@ class AccountCustomClientTest extends Scope public function testCreatePhone(): array { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } + $number = '+123456789'; - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { - $this->markTestSkipped('SMS provider not configured'); - } - - $number = $to; - $response = $this->client->call(Client::METHOD_POST, '/messaging/providers/msg91', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'providerId' => ID::unique(), - 'name' => 'Sms provider', - 'senderId' => $senderId, - 'authKey' => $authKey, - 'from' => $from, - ]); - $this->assertEquals(201, $response['headers']['status-code']); /** * Test for SUCCESS */ @@ -781,7 +756,6 @@ class AccountCustomClientTest extends Scope ]), [ 'userId' => ID::unique(), 'phone' => $number, - 'from' => $from, ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -807,19 +781,17 @@ class AccountCustomClientTest extends Scope \sleep(5); - $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + $smsRequest = $this->getLastRequest(); - $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTotal']); - $this->assertEquals(0, \count($message['body']['deliveryErrors'])); + $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); + $this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']); + $this->assertEquals('username', $smsRequest['headers']['X-Username']); + $this->assertEquals('password', $smsRequest['headers']['X-Key']); + $this->assertEquals('POST', $smsRequest['method']); + $this->assertEquals('+123456789', $smsRequest['data']['from']); + $this->assertEquals($number, $smsRequest['data']['to']); - - $data['token'] = $message['body']['data']['content']; + $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; $data['number'] = $number; @@ -1018,8 +990,6 @@ class AccountCustomClientTest extends Scope public function testPhoneVerification(array $data): array { $session = $data['session'] ?? ''; - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $from = $smsDSN->getParam('from'); /** * Test for SUCCESS @@ -1030,28 +1000,19 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), ['from' => $from]); + ])); $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); - \sleep(3); + \sleep(2); - $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $response['body']['$id'], [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTotal']); - $this->assertEquals(0, \count($message['body']['deliveryErrors'])); + $smsRequest = $this->getLastRequest(); return \array_merge($data, [ - 'token' => $message['body']['data']['content'] + 'token' => $smsRequest['data']['secret'] ]); } diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 9d1da09feb..3e2cfac29c 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -124,38 +124,7 @@ class AccountTest extends Scope */ public function testCreatePhoneVerification(): array { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } - - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { - $this->markTestSkipped('SMS provider not configured'); - } - $projectId = $this->getProject()['$id']; - $query = $this->getQuery(self::$CREATE_MSG91_PROVIDER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'Sms Provider', - 'from' => $from, - 'senderId' => $senderId, - 'authKey' => $authKey, - ], - ]; - - $response = $this->client->call(Client::METHOD_POST, '/graphql', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $graphQLPayload); $query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION); $graphQLPayload = [ diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index b57b680674..707905892c 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -1796,7 +1796,6 @@ trait Base name provider type - internal enabled } }'; @@ -1807,7 +1806,6 @@ trait Base name provider type - internal enabled } }'; @@ -1818,7 +1816,6 @@ trait Base name provider type - internal enabled } }'; @@ -1829,7 +1826,6 @@ trait Base name provider type - internal enabled } }'; @@ -1840,7 +1836,6 @@ trait Base name provider type - internal enabled } }'; @@ -1851,7 +1846,6 @@ trait Base name provider type - internal enabled } }'; @@ -1862,7 +1856,6 @@ trait Base name provider type - internal enabled } }'; @@ -1873,7 +1866,6 @@ trait Base name provider type - internal enabled } }'; @@ -1884,7 +1876,6 @@ trait Base name provider type - internal enabled } }'; @@ -1897,7 +1888,7 @@ trait Base name provider type - internal + enabled } } @@ -1909,7 +1900,6 @@ trait Base name provider type - internal enabled } }'; @@ -1920,7 +1910,6 @@ trait Base name provider type - internal enabled } }'; @@ -1931,7 +1920,6 @@ trait Base name provider type - internal enabled } }'; @@ -1942,7 +1930,6 @@ trait Base name provider type - internal enabled } }'; @@ -1953,7 +1940,6 @@ trait Base name provider type - internal enabled } }'; @@ -1964,7 +1950,6 @@ trait Base name provider type - internal enabled } }'; @@ -1975,7 +1960,6 @@ trait Base name provider type - internal enabled } }'; @@ -1986,7 +1970,6 @@ trait Base name provider type - internal enabled } }'; @@ -1997,7 +1980,6 @@ trait Base name provider type - internal enabled } }'; @@ -2008,7 +1990,6 @@ trait Base name provider type - internal enabled } }'; From 549dcc493dc0703505546c473147db6d635264d0 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 01:33:05 +0530 Subject: [PATCH 43/54] lint fix --- src/Appwrite/Event/Messaging.php | 4 ++-- src/Appwrite/Platform/Workers/Messaging.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 354418486b..62d41f8c3b 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -13,7 +13,7 @@ class Messaging extends Event protected ?array $recipients = null; protected ?string $deliveryTime = null; protected ?string $providerType = null; - + public function __construct(protected Connection $connection) { @@ -170,4 +170,4 @@ class Messaging extends Event 'providerType' => $this->providerType, ]); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index b2b0094a88..2283770e09 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -77,7 +77,7 @@ class Messaging extends Action } } - + private function processMessage(Database $dbForProject, Document $message): void { @@ -228,7 +228,7 @@ class Messaging extends Action private function processInternalSMSMessage(Document $message, array $recipients): void { - if(empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) { + 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; } From c728d9bc8e07fd0431ffa2924158fe99052303e3 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 01:36:22 +0530 Subject: [PATCH 44/54] lint fix --- src/Appwrite/Platform/Workers/Messaging.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 2283770e09..299ef4526f 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -77,8 +77,6 @@ class Messaging extends Action } } - - private function processMessage(Database $dbForProject, Document $message): void { $topicsId = $message->getAttribute('topics', []); @@ -273,6 +271,7 @@ class Messaging extends Action 'from' => $from ] ]); + $adapter = $this->sms($provider); $maxBatchSize = $adapter->getMaxMessagesPerRequest(); From a282ab38a6f42a718ce7c2132f134f55450d2026 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 02:12:06 +0530 Subject: [PATCH 45/54] adds target endpoint in account controller for push tokensl --- app/config/collections.php | 11 +++ app/controllers/api/account.php | 153 ++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index 0d42dfe895..0c67cd175f 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1870,6 +1870,17 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['json'], + ], ], 'indexes' => [ [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 6486147a84..71a5562ae3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1677,6 +1677,96 @@ App::post('/v1/account/jwt') ])]), Response::MODEL_JWT); }); +App::post('/v1/account/targets') + ->desc('Create Account 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('scope', 'public') + ->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('providerType', '', new WhiteList(['email', 'sms', '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(256), 'Set Client device name manually instead of using user agent. Used to identify the client device make, model, OS, etc.', true) + ->inject('queueForEvents') + ->inject('user') + ->inject('request') + ->inject('response') + ->inject('dbForProject') + ->action(function (string $targetId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { + $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; + + $provider = new Document(); + + if ($providerType === 'push') { + $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) + + $os = $detector->getOS(); + $client = $detector->getClient(); + $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' => $providerType, + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'identifier' => $identifier, + 'name' => $name ?? [ + 'osCode' => $os['osCode'], + 'osName' => $os['osName'], + 'osVersion' => $os['osVersion'], + 'clientType' => $client['clientType'], + 'clientCode' => $client['clientCode'], + 'clientName' => $client['clientName'], + 'clientVersion' => $client['clientVersion'], + 'clientEngine' => $client['clientEngine'], + 'clientEngineVersion' => $client['clientEngineVersion'], + 'deviceName' => $device['deviceName'], + 'deviceBrand' => $device['deviceBrand'], + 'deviceModel' => $device['deviceModel'] + ] + ])); + } catch (Duplicate) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + Authorization::skip(fn () => $dbForProject->deleteCachedDocument('users', $user->getId())); + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('targetId', $target->getId()); + + $response + ->dynamic($target, Response::MODEL_TARGET); + }); + App::get('/v1/account') ->desc('Get account') ->groups(['api', 'account']) @@ -3077,3 +3167,66 @@ App::put('/v1/account/verification/phone') $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); + +App::put('/v1/account/targets/:targetId') + ->desc('Update Account 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].create') + ->label('scope', 'public') + ->label('docs', false) + ->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(256), 'Set Client device name manually instead of using user agent. Used to identify the client device make, model, OS, etc.', true) + ->inject('queueForEvents') + ->inject('user') + ->inject('request') + ->inject('response') + ->inject('dbForProject') + ->action(function (string $targetId, string $identifier, string $providerId, string $name, 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); + } + + if ($providerId) { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $target->setAttribute('providerId', $provider->getId()); + $target->setAttribute('providerInternalId', $provider->getInternalId()); + } + + if ($name) { + $target->setAttribute('name', $name); + } + + $target = $dbForProject->updateDocument('targets', $target->getId(), $target); + Authorization::skip(fn () => $dbForProject->deleteCachedDocument('users', $user->getId())); + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('targetId', $target->getId()); + + $response + ->dynamic($target, Response::MODEL_TARGET); + }); From c1869bb0caf62cf89829d2e495981df4a86d72e7 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 11:20:39 +0530 Subject: [PATCH 46/54] adds messaging in test workflow --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 842d61ff1c..14e1ac5e44 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -99,6 +99,7 @@ jobs: Users, Webhooks, VCS, + Messaging, ] steps: From f85462270584982d4b928a8fa2abb2493973ea93 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 16:26:36 +0530 Subject: [PATCH 47/54] review changes --- app/controllers/api/account.php | 63 ++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 71a5562ae3..154e385c76 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1677,36 +1677,35 @@ App::post('/v1/account/jwt') ])]), Response::MODEL_JWT); }); -App::post('/v1/account/targets') +App::post('/v1/account/targets/push') ->desc('Create Account 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('scope', 'public') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION]) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'createTarget') + ->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('providerType', '', new WhiteList(['email', 'sms', 'push']), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.') + ->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.)') - ->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(256), 'Set Client device name manually instead of using user agent. Used to identify the client device make, model, OS, etc.', true) ->inject('queueForEvents') ->inject('user') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Document $user, Request $request, Response $response, Database $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 = new Document(); + $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); - if ($providerType === 'push') { - $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); - - if ($provider->isEmpty()) { - throw new Exception(Exception::PROVIDER_NOT_FOUND); - } + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); } if ($user->isEmpty()) { @@ -1735,11 +1734,11 @@ App::post('/v1/account/targets') ], 'providerId' => $providerId ?? null, 'providerInternalId' => $provider->getInternalId() ?? null, - 'providerType' => $providerType, + 'providerType' => 'push', 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'identifier' => $identifier, - 'name' => $name ?? [ + 'name' => [ 'osCode' => $os['osCode'], 'osName' => $os['osName'], 'osVersion' => $os['osVersion'], @@ -1749,7 +1748,6 @@ App::post('/v1/account/targets') 'clientVersion' => $client['clientVersion'], 'clientEngine' => $client['clientEngine'], 'clientEngineVersion' => $client['clientEngineVersion'], - 'deviceName' => $device['deviceName'], 'deviceBrand' => $device['deviceBrand'], 'deviceModel' => $device['deviceModel'] ] @@ -1757,13 +1755,14 @@ App::post('/v1/account/targets') } catch (Duplicate) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } - Authorization::skip(fn () => $dbForProject->deleteCachedDocument('users', $user->getId())); + $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); }); @@ -3168,7 +3167,7 @@ App::put('/v1/account/verification/phone') $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); -App::put('/v1/account/targets/:targetId') +App::put('/v1/account/targets/:targetId/push') ->desc('Update Account Target') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -3180,13 +3179,12 @@ App::put('/v1/account/targets/:targetId') ->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(256), 'Set Client device name manually instead of using user agent. Used to identify the client device make, model, OS, etc.', true) ->inject('queueForEvents') ->inject('user') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } @@ -3216,12 +3214,29 @@ App::put('/v1/account/targets/:targetId') $target->setAttribute('providerInternalId', $provider->getInternalId()); } - if ($name) { - $target->setAttribute('name', $name); - } + $detector = new Detector($request->getUserAgent()); + $detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) + + $os = $detector->getOS(); + $client = $detector->getClient(); + $device = $detector->getDevice(); + + $target->setAttribute('name', [ + 'osCode' => $os['osCode'], + 'osName' => $os['osName'], + 'osVersion' => $os['osVersion'], + 'clientType' => $client['clientType'], + 'clientCode' => $client['clientCode'], + 'clientName' => $client['clientName'], + 'clientVersion' => $client['clientVersion'], + 'clientEngine' => $client['clientEngine'], + 'clientEngineVersion' => $client['clientEngineVersion'], + 'deviceBrand' => $device['deviceBrand'], + 'deviceModel' => $device['deviceModel'] + ]); $target = $dbForProject->updateDocument('targets', $target->getId(), $target); - Authorization::skip(fn () => $dbForProject->deleteCachedDocument('users', $user->getId())); + $dbForProject->deleteCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) From bf95736da0db399588ba6f028254bab63f09c787 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 16:47:36 +0530 Subject: [PATCH 48/54] review changes --- app/controllers/api/account.php | 61 +++++++-------------------------- 1 file changed, 13 insertions(+), 48 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 154e385c76..45beab5ec6 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1678,7 +1678,7 @@ App::post('/v1/account/jwt') }); App::post('/v1/account/targets/push') - ->desc('Create Account Target') + ->desc('Create Account\'s push target') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') ->label('audits.event', 'target.create') @@ -1686,7 +1686,7 @@ App::post('/v1/account/targets/push') ->label('event', 'users.[userId].targets.[targetId].create') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION]) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'createTarget') + ->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) @@ -1721,8 +1721,6 @@ App::post('/v1/account/targets/push') $detector = new Detector($request->getUserAgent()); $detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - $os = $detector->getOS(); - $client = $detector->getClient(); $device = $detector->getDevice(); try { @@ -1738,19 +1736,7 @@ App::post('/v1/account/targets/push') 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'identifier' => $identifier, - 'name' => [ - 'osCode' => $os['osCode'], - 'osName' => $os['osName'], - 'osVersion' => $os['osVersion'], - 'clientType' => $client['clientType'], - 'clientCode' => $client['clientCode'], - 'clientName' => $client['clientName'], - 'clientVersion' => $client['clientVersion'], - 'clientEngine' => $client['clientEngine'], - 'clientEngineVersion' => $client['clientEngineVersion'], - 'deviceBrand' => $device['deviceBrand'], - 'deviceModel' => $device['deviceModel'] - ] + 'name' => "{$device['deviceBrand']} {$device['deviceModel']}" ])); } catch (Duplicate) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); @@ -3168,23 +3154,27 @@ App::put('/v1/account/verification/phone') }); App::put('/v1/account/targets/:targetId/push') - ->desc('Update Account Target') + ->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].create') - ->label('scope', 'public') + ->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.)', 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) ->inject('queueForEvents') ->inject('user') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $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); } @@ -3203,37 +3193,12 @@ App::put('/v1/account/targets/:targetId/push') $target->setAttribute('identifier', $identifier); } - if ($providerId) { - $provider = $dbForProject->getDocument('providers', $providerId); - - if ($provider->isEmpty()) { - throw new Exception(Exception::PROVIDER_NOT_FOUND); - } - - $target->setAttribute('providerId', $provider->getId()); - $target->setAttribute('providerInternalId', $provider->getInternalId()); - } - $detector = new Detector($request->getUserAgent()); $detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - $os = $detector->getOS(); - $client = $detector->getClient(); $device = $detector->getDevice(); - $target->setAttribute('name', [ - 'osCode' => $os['osCode'], - 'osName' => $os['osName'], - 'osVersion' => $os['osVersion'], - 'clientType' => $client['clientType'], - 'clientCode' => $client['clientCode'], - 'clientName' => $client['clientName'], - 'clientVersion' => $client['clientVersion'], - 'clientEngine' => $client['clientEngine'], - 'clientEngineVersion' => $client['clientEngineVersion'], - 'deviceBrand' => $device['deviceBrand'], - 'deviceModel' => $device['deviceModel'] - ]); + $target->setAttribute('name', "{$device['deviceBrand']} {$device['deviceModel']}"); $target = $dbForProject->updateDocument('targets', $target->getId(), $target); $dbForProject->deleteCachedDocument('users', $user->getId()); From 7536d8eb0feb3f1c097941385a4f3e7b1e85c0c6 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 16 Nov 2023 17:09:08 +0530 Subject: [PATCH 49/54] review changes --- app/config/collections.php | 4 ++-- app/controllers/api/account.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 0c67cd175f..0596faece7 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1874,12 +1874,12 @@ $commonCollections = [ '$id' => ID::custom('name'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 16384, + 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, - 'filters' => ['json'], + 'filters' => [''], ], ], 'indexes' => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 45beab5ec6..783b8151a4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3168,7 +3168,7 @@ App::put('/v1/account/targets/:targetId/push') ->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.)', true) + ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)') ->inject('queueForEvents') ->inject('user') ->inject('request') From f0247930dab35a47e9f1534967cf8598ef20cd32 Mon Sep 17 00:00:00 2001 From: Prateek Banga <30731059+fanatic75@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:15:08 +0530 Subject: [PATCH 50/54] Update app/config/collections.php Co-authored-by: Jake Barnby --- app/config/collections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/collections.php b/app/config/collections.php index 0596faece7..7e715baa2c 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1879,7 +1879,7 @@ $commonCollections = [ 'required' => false, 'default' => null, 'array' => false, - 'filters' => [''], + 'filters' => [], ], ], 'indexes' => [ From dceb1858b0698ffc3ae7291012ad615257ffbbc7 Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 16 Nov 2023 20:31:53 +0200 Subject: [PATCH 51/54] comparison fix --- src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 5f0ecbe1db..26ada31416 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -90,7 +90,7 @@ class DeleteOrphanedProjects extends Action /** * +2 = audit+abuse */ - if ($collectionsCreated === (count($collectionsConfig) + 2)) { + if ($collectionsCreated >= (count($collectionsConfig) + 2)) { Console::log($msg . ' ignoring....'); continue; } From 8c9a662e1427d5beedd0ff63338c87bcdef3a4f4 Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 16 Nov 2023 20:33:37 +0200 Subject: [PATCH 52/54] comparison fix --- src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 26ada31416..753240b66a 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -54,7 +54,7 @@ class DeleteOrphanedProjects extends Action $totalProjects = $dbForConsole->count('projects'); Console::success("Found a total of: {$totalProjects} projects"); - $orphans = 1; + $orphans = 0; $cnt = 0; $count = 0; $limit = 30; @@ -135,6 +135,6 @@ class DeleteOrphanedProjects extends Action $count = $count + $sum; } - Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans'); + Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans . ' orphans'); } } From 5c1cf3a526544bb2ef38f4e0461b881b9740dac1 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Mon, 20 Nov 2023 12:34:50 +0530 Subject: [PATCH 53/54] adds user name in subscriber response model --- app/config/collections.php | 36 +++++++++++++++++++ app/controllers/api/messaging.php | 36 ++++++++++++++++--- .../Utopia/Response/Model/Subscriber.php | 12 +++++++ tests/e2e/Services/GraphQL/Base.php | 3 ++ tests/e2e/Services/GraphQL/MessagingTest.php | 15 ++++++-- .../e2e/Services/Messaging/MessagingBase.php | 8 +++++ 6 files changed, 103 insertions(+), 7 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 7e715baa2c..f310c91211 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1722,6 +1722,28 @@ $commonCollections = [ '$id' => ID::custom('subscribers'), 'name' => 'Subscribers', 'attributes' => [ + [ + '$id' => ID::custom('userId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('userInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('targetId'), 'type' => Database::VAR_STRING, @@ -1768,6 +1790,20 @@ $commonCollections = [ ], ], 'indexes' => [ + [ + '$id' => ID::custom('_key_userId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['userId'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_userInternalId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['userInternalId'], + 'lengths' => [], + 'orders' => [], + ], [ '$id' => ID::custom('_key_targetId'), 'type' => Database::INDEX_KEY, diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index e1e531c1a1..d6b3571508 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -36,6 +36,8 @@ use Utopia\Validator\Text; use MaxMind\Db\Reader; use Utopia\Validator\WhiteList; +use function Swoole\Coroutine\batch; + App::post('/v1/messaging/providers/mailgun') ->desc('Create Mailgun provider') ->groups(['api', 'messaging']) @@ -1644,12 +1646,16 @@ App::post('/v1/messaging/topics/:topicId/subscribers') throw new Exception(Exception::USER_TARGET_NOT_FOUND); } + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $subscriber = new Document([ '$id' => $subscriberId, '$permissions' => [ - Permission::read(Role::user($target->getAttribute('userId'))), + Permission::read(Role::user($user->getId())), Permission::delete(Role::user($target->getAttribute('userId'))), ], + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), 'topicId' => $topicId, 'topicInternalId' => $topic->getInternalId(), 'targetId' => $targetId, @@ -1669,7 +1675,12 @@ App::post('/v1/messaging/topics/:topicId/subscribers') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); + ->dynamic( + $subscriber + ->setAttribute('userId', $user->getId()) + ->setAttribute('userName', $user->getAttribute('name')), + Response::MODEL_SUBSCRIBER + ); }); App::get('/v1/messaging/topics/:topicId/subscribers') @@ -1713,9 +1724,20 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $cursor->setValue($cursorDocument); } + $subscribers = $dbForProject->find('subscribers', $queries); + + $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { + return function () use ($subscriber, $dbForProject) { + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); + + return $subscriber + ->setAttribute('userName', $user->getAttribute('name')); + }; + }, $subscribers)); + $response ->dynamic(new Document([ - 'subscribers' => $dbForProject->find('subscribers', $queries), + 'subscribers' => $subscribers, 'total' => $dbForProject->count('subscribers', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_SUBSCRIBER_LIST); }); @@ -1832,8 +1854,14 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); } + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); + $response - ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); + ->dynamic( + $subscriber + ->setAttribute('userName', $user->getAttribute('name')), + Response::MODEL_SUBSCRIBER + ); }); App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index 65bbd38f0e..71e6fedff3 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -34,6 +34,18 @@ class Subscriber extends Model 'default' => '', 'example' => '259125845563242502', ]) + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'User ID.', + 'default' => '', + 'example' => '259125845563242502', + ]) + ->addRule('userName', [ + 'type' => self::TYPE_STRING, + 'description' => 'User Name.', + 'default' => '', + 'example' => 'Aegon Targaryen', + ]) ->addRule('topicId', [ 'type' => self::TYPE_STRING, 'description' => 'Topic ID.', diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 707905892c..8655395dc2 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2044,6 +2044,7 @@ trait Base return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) { messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) { _id + userId targetId topicId } @@ -2054,6 +2055,7 @@ trait Base total subscribers { _id + userId targetId topicId } @@ -2063,6 +2065,7 @@ trait Base return 'query getSubscriber($topicId: String!, $subscriberId: String!) { messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) { _id + userId targetId topicId } diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 112e9e5633..27b2b52cd3 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -428,20 +428,23 @@ class MessagingTest extends Scope ], $this->getHeaders()), $graphQLPayload); $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['topicId'], $topicId); + $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['targetId'], $targetId); + $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['userId'], $userId); return $response['body']['data']['messagingCreateSubscriber']; } /** - * @depends testUpdateTopic + * @depends testCreateSubscriber */ - public function testListSubscribers(string $topicId) + public function testListSubscribers(array $subscriber) { $query = $this->getQuery(self::$LIST_SUBSCRIBERS); $graphQLPayload = [ 'query' => $query, 'variables' => [ - 'topicId' => $topicId, + 'topicId' => $subscriber['topicId'], ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -451,6 +454,9 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['topicId'], $subscriber['topicId']); + $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['targetId'], $subscriber['targetId']); + $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['userId'], $subscriber['userId']); $this->assertEquals(1, \count($response['body']['data']['messagingListSubscribers']['subscribers'])); } @@ -479,6 +485,9 @@ class MessagingTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($subscriberId, $response['body']['data']['messagingGetSubscriber']['_id']); + $this->assertEquals($topicId, $response['body']['data']['messagingGetSubscriber']['topicId']); + $this->assertEquals($subscriber['targetId'], $response['body']['data']['messagingGetSubscriber']['targetId']); + $this->assertEquals($subscriber['userId'], $response['body']['data']['messagingGetSubscriber']['userId']); } /** diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index daafd52e8f..1f09d95522 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -321,6 +321,7 @@ trait MessagingBase 'providerId' => $provider['body']['$id'], 'identifier' => 'my-token', ]); + $this->assertEquals(201, $target['headers']['status-code']); $response = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['$id'] . '/subscribers', \array_merge([ @@ -330,13 +331,16 @@ trait MessagingBase 'subscriberId' => ID::unique(), 'targetId' => $target['body']['$id'], ]); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($target['body']['userId'], $response['body']['userId']); $topic = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]); + $this->assertEquals(200, $topic['headers']['status-code']); $this->assertEquals('android-app', $topic['body']['name']); $this->assertEquals('updated-description', $topic['body']['description']); @@ -345,6 +349,7 @@ trait MessagingBase return [ 'topicId' => $topic['body']['$id'], 'targetId' => $target['body']['$id'], + 'userId' => $target['body']['userId'], 'subscriberId' => $response['body']['$id'] ]; } @@ -359,9 +364,11 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ])); + $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($data['topicId'], $response['body']['topicId']); $this->assertEquals($data['targetId'], $response['body']['targetId']); + $this->assertEquals($data['userId'], $response['body']['userId']); } /** @@ -377,6 +384,7 @@ trait MessagingBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); + $this->assertEquals($data['userId'], $response['body']['subscribers'][0]['userId']); $this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']); return $data; From b1eaf978063c0d2c518f34de633dab3ea9e52b04 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Mon, 20 Nov 2023 16:21:06 +0530 Subject: [PATCH 54/54] review changes --- app/controllers/api/messaging.php | 17 ++++++----------- .../Utopia/Response/Model/Subscriber.php | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index d6b3571508..35ba97c1af 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1673,14 +1673,11 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->setParam('topicId', $topic->getId()) ->setParam('subscriberId', $subscriber->getId()); + $subscriber->setAttribute('userName', $user->getAttribute('name')); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic( - $subscriber - ->setAttribute('userId', $user->getId()) - ->setAttribute('userName', $user->getAttribute('name')), - Response::MODEL_SUBSCRIBER - ); + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); App::get('/v1/messaging/topics/:topicId/subscribers') @@ -1856,12 +1853,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); + $subscriber->setAttribute('userName', $user->getAttribute('name')); + $response - ->dynamic( - $subscriber - ->setAttribute('userName', $user->getAttribute('name')), - Response::MODEL_SUBSCRIBER - ); + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index 71e6fedff3..5080b44333 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -38,7 +38,7 @@ class Subscriber extends Model 'type' => self::TYPE_STRING, 'description' => 'User ID.', 'default' => '', - 'example' => '259125845563242502', + 'example' => '5e5ea5c16897e', ]) ->addRule('userName', [ 'type' => self::TYPE_STRING,