From 85a40bf82d3fbef959d803d1ce9155765efd5d7b Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 26 May 2023 17:15:38 -0700 Subject: [PATCH 1/6] Add a new labels attribute to the Users collection This labels attribute can be used apply labels to users. These labels can eventually be used: * with permissions to grant access * to filter users in the Users collection --- app/config/collections.php | 11 +++++++++++ src/Appwrite/Utopia/Response/Model/User.php | 7 +++++++ tests/e2e/Services/Account/AccountBase.php | 1 + tests/e2e/Services/Users/UsersBase.php | 1 + 4 files changed, 20 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index 0b4dda6851..c7572ddad7 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1244,6 +1244,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('labels'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], [ '$id' => ID::custom('passwordHistory'), 'type' => Database::VAR_STRING, diff --git a/src/Appwrite/Utopia/Response/Model/User.php b/src/Appwrite/Utopia/Response/Model/User.php index 648b1c5dce..0fa07cb79a 100644 --- a/src/Appwrite/Utopia/Response/Model/User.php +++ b/src/Appwrite/Utopia/Response/Model/User.php @@ -77,6 +77,13 @@ class User extends Model 'default' => true, 'example' => true, ]) + ->addRule('labels', [ + 'type' => self::TYPE_STRING, + 'description' => 'Labels for the user.', + 'default' => [], + 'example' => ['vip'], + 'array' => true, + ]) ->addRule('passwordUpdate', [ 'type' => self::TYPE_DATETIME, 'description' => 'Password update time in ISO 8601 format.', diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index ba751516d3..7665b10dc0 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -39,6 +39,7 @@ trait AccountBase $this->assertEquals(true, $dateValidator->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $this->assertEquals($response['body']['name'], $name); + $this->assertEquals($response['body']['labels'], []); /** * Test for FAILURE diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 9f14c59306..cb83faea82 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -35,6 +35,7 @@ trait UsersBase $this->assertEquals($body['email'], 'cristiano.ronaldo@manchester-united.co.uk'); $this->assertEquals($body['status'], true); $this->assertGreaterThan('2000-01-01 00:00:00', $body['registration']); + $this->assertEquals($body['labels'], []); /** * Test Create with Custom ID for SUCCESS From 8400394857b65803f451dfdc287bf2162487fff5 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 26 May 2023 17:23:01 -0700 Subject: [PATCH 2/6] Add an endpoint to update user labels --- app/controllers/api/users.php | 41 +++++++++ docs/references/users/update-user-labels.md | 3 + tests/e2e/Services/Users/UsersBase.php | 93 +++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 docs/references/users/update-user-labels.md diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index ceed901a32..4441161e8f 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -28,6 +28,7 @@ use Utopia\Database\Validator\UID; use Utopia\Database\Database; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\WhiteList; use Utopia\Validator\Text; @@ -65,6 +66,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'phone' => $phone, 'phoneVerification' => false, 'status' => true, + 'labels' => [], 'password' => $password, 'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password], 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, @@ -663,6 +665,45 @@ App::patch('/v1/users/:userId/status') $response->dynamic($user, Response::MODEL_USER); }); +App::put('/v1/users/:userId/labels') + ->desc('Update User Labels') + ->groups(['api', 'users']) + ->label('event', 'users.[userId].update.labels') + ->label('scope', 'users.write') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('usage.metric', 'users.{scope}.requests.update') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'users') + ->label('sdk.method', 'updateLabels') + ->label('sdk.description', '/docs/references/users/update-user-labels.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_USER) + ->param('userId', '', new UID(), 'User ID.') + ->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), 5), 'Array of user labels. Replaces the previous labels. Maximum of 5 labels are allowed, each up to 36 alphanumeric characters long.') + ->inject('response') + ->inject('dbForProject') + ->inject('events') + ->action(function (string $userId, array $labels, Response $response, Database $dbForProject, Event $events) { + + $user = $dbForProject->getDocument('users', $userId); + + if ($user->isEmpty()) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + $user->setAttribute('labels', (array) \array_values(\array_unique($labels))); + + $user = $dbForProject->updateDocument('users', $user->getId(), $user); + + $events + ->setParam('userId', $user->getId()); + + $response->dynamic($user, Response::MODEL_USER); + }); + App::patch('/v1/users/:userId/verification') ->desc('Update Email Verification') ->groups(['api', 'users']) diff --git a/docs/references/users/update-user-labels.md b/docs/references/users/update-user-labels.md new file mode 100644 index 0000000000..3ff0388eae --- /dev/null +++ b/docs/references/users/update-user-labels.md @@ -0,0 +1,3 @@ +Update the user labels by its unique ID. + +Labels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](/docs/permissions) for more info. \ No newline at end of file diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index cb83faea82..baf601789a 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Users; use Appwrite\Tests\Retry; +use Appwrite\Utopia\Response; use Tests\E2E\Client; use Utopia\Database\Helpers\ID; @@ -1016,6 +1017,98 @@ trait UsersBase $this->assertEquals($response['body']['users'][0]['phone'], $newNumber); } + /** + * @return array{} + */ + public function userLabelsProvider() + { + return [ + 'single label' => [ + ['admin'], + Response::STATUS_CODE_OK, + ['admin'], + ], + 'replace with multiple labels' => [ + ['vip', 'pro'], + Response::STATUS_CODE_OK, + ['vip', 'pro'], + ], + 'clear labels' => [ + [], + Response::STATUS_CODE_OK, + [], + ], + 'duplicate labels' => [ + ['vip', 'vip', 'pro'], + Response::STATUS_CODE_OK, + ['vip', 'pro'], + ], + 'invalid label' => [ + ['invalid-label'], + Response::STATUS_CODE_BAD_REQUEST, + [], + ], + 'too long' => [ + [\str_repeat('a', 129)], + Response::STATUS_CODE_BAD_REQUEST, + [], + ], + 'too many labels' => [ + [\array_fill(0, 101, 'a')], + Response::STATUS_CODE_BAD_REQUEST, + [], + ], + ]; + } + + /** + * @depends testGetUser + * @dataProvider userLabelsProvider + */ + public function testUpdateUserLabels(array $labels, int $expectedStatus, array $expectedLabels, array $data): array + { + $user = $this->client->call(Client::METHOD_PUT, '/users/' . $data['userId'] . '/labels', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'labels' => $labels, + ]); + + $this->assertEquals($expectedStatus, $user['headers']['status-code']); + if ($expectedStatus === Response::STATUS_CODE_OK) { + $this->assertEquals($user['body']['labels'], $expectedLabels); + } + + return $data; + } + + /** + * @depends testGetUser + */ + public function testUpdateUserLabelsWithoutLabels(array $data): array + { + $user = $this->client->call(Client::METHOD_PUT, '/users/' . $data['userId'] . '/labels', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(Response::STATUS_CODE_BAD_REQUEST, $user['headers']['status-code']); + + return $data; + } + + public function testUpdateUserLabelsNonExistentUser(): void + { + $user = $this->client->call(Client::METHOD_PUT, '/users/dne/labels', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'labels' => ['admin'], + ]); + + $this->assertEquals(Response::STATUS_CODE_NOT_FOUND, $user['headers']['status-code']); + } + /** * @depends testGetUser From 078dab38c55d3dae873998df90a5debac998fcea Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 26 May 2023 17:23:28 -0700 Subject: [PATCH 3/6] Fix the team membership model's roles example --- src/Appwrite/Utopia/Response/Model/Membership.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Membership.php b/src/Appwrite/Utopia/Response/Model/Membership.php index 118864e804..c134142185 100644 --- a/src/Appwrite/Utopia/Response/Model/Membership.php +++ b/src/Appwrite/Utopia/Response/Model/Membership.php @@ -80,7 +80,7 @@ class Membership extends Model 'type' => self::TYPE_STRING, 'description' => 'User list of roles', 'default' => [], - 'example' => 'admin', + 'example' => ['owner'], 'array' => true, ]) ; From 09f357725697cf8e2b0d2896ec8658a597611a46 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 26 May 2023 17:40:30 -0700 Subject: [PATCH 4/6] Update Appwrite to add User labels to Auth roles --- src/Appwrite/Auth/Auth.php | 4 ++++ tests/unit/Auth/AuthTest.php | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 7217a87680..25c6b36e08 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -456,6 +456,10 @@ class Auth } } + foreach ($user->getAttribute('labels', []) as $label) { + $roles[] = 'label:' . $label; + } + return $roles; } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index e64ef37dfe..aa8ff492f6 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -352,6 +352,10 @@ class AuthTest extends TestCase { $user = new Document([ '$id' => ID::custom('123'), + 'labels' => [ + 'vip', + 'admin' + ], 'emailVerification' => true, 'phoneVerification' => true, 'memberships' => [ @@ -377,7 +381,7 @@ class AuthTest extends TestCase $roles = Auth::getRoles($user); - $this->assertCount(11, $roles); + $this->assertCount(13, $roles); $this->assertContains(Role::users()->toString(), $roles); $this->assertContains(Role::user(ID::custom('123'))->toString(), $roles); $this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles); @@ -389,6 +393,8 @@ class AuthTest extends TestCase $this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles); $this->assertContains(Role::member(ID::custom('456'))->toString(), $roles); $this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles); + $this->assertContains('label:vip', $roles); + $this->assertContains('label:admin', $roles); // Disable all verification $user['emailVerification'] = false; From b3c9a34e5eb63d2650985ac7ed33e704de1d383c Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Tue, 30 May 2023 13:55:33 -0700 Subject: [PATCH 5/6] Make users searchable by label --- app/controllers/api/account.php | 20 ++++++++++++-------- app/controllers/api/users.php | 25 +++++++++++++++---------- app/controllers/shared/api.php | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 85d59bd028..02d70f24e9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1529,11 +1529,11 @@ App::patch('/v1/account/name') ->inject('user') ->inject('dbForProject') ->inject('events') - ->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { - $user - ->setAttribute('name', $name) - ->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])); + $user->setAttribute('name', $name); + $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); @@ -1628,7 +1628,8 @@ App::patch('/v1/account/email') ->inject('user') ->inject('dbForProject') ->inject('events') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -1644,7 +1645,8 @@ App::patch('/v1/account/email') $user ->setAttribute('email', $email) ->setAttribute('emailVerification', false) // After this user needs to confirm mail again - ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')])); + ; + $user->setAttribute('search', $prepareUserSearch($user)); if (empty($passwordUpdate)) { $user @@ -1690,7 +1692,8 @@ App::patch('/v1/account/phone') ->inject('user') ->inject('dbForProject') ->inject('events') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -1704,7 +1707,8 @@ App::patch('/v1/account/phone') $user ->setAttribute('phone', $phone) ->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again - ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone])); + ; + $user->setAttribute('search', $prepareUserSearch($user)); if (empty($passwordUpdate)) { $user diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4441161e8f..dd1c649567 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -686,7 +686,8 @@ App::put('/v1/users/:userId/labels') ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, array $labels, Response $response, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $userId, array $labels, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { $user = $dbForProject->getDocument('users', $userId); @@ -695,6 +696,7 @@ App::put('/v1/users/:userId/labels') } $user->setAttribute('labels', (array) \array_values(\array_unique($labels))); + $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -797,7 +799,8 @@ App::patch('/v1/users/:userId/name') ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $name, Response $response, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $userId, string $name, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { $user = $dbForProject->getDocument('users', $userId); @@ -805,10 +808,8 @@ App::patch('/v1/users/:userId/name') throw new Exception(Exception::USER_NOT_FOUND); } - $user - ->setAttribute('name', $name) - ->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email', ''), $name, $user->getAttribute('phone', '')])); - ; + $user->setAttribute('name', $name); + $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -897,7 +898,8 @@ App::patch('/v1/users/:userId/email') ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, Response $response, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $userId, string $email, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { $user = $dbForProject->getDocument('users', $userId); @@ -910,7 +912,9 @@ App::patch('/v1/users/:userId/email') $user ->setAttribute('email', $email) ->setAttribute('emailVerification', false) - ->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')])); + ; + + $user->setAttribute('search', $prepareUserSearch($user)); try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -943,7 +947,8 @@ App::patch('/v1/users/:userId/phone') ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $number, Response $response, Database $dbForProject, Event $events) { + ->inject('prepareUserSearch') + ->action(function (string $userId, string $number, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { $user = $dbForProject->getDocument('users', $userId); @@ -954,8 +959,8 @@ App::patch('/v1/users/:userId/phone') $user ->setAttribute('phone', $number) ->setAttribute('phoneVerification', false) - ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $number])); ; + $user->setAttribute('search', $prepareUserSearch($user)); try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 93cf81e1fa..fcc12a3a2a 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -87,6 +87,25 @@ $databaseListener = function (string $event, Document $document, Stats $usage) { } }; +App::setResource('prepareUserSearch', function () { + return function (Document $user): string { + $searchValues = [ + $user->getId(), + $user->getAttribute('email', ''), + $user->getAttribute('name', ''), + $user->getAttribute('phone', '') + ]; + + foreach ($user->getAttribute('labels', []) as $label) { + $searchValues[] = 'label:' . $label; + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }; +}); + App::init() ->groups(['api']) ->inject('utopia') From 8258d70b78f8b6d0134bcee4c8c5158d8ca55ff9 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Wed, 5 Jul 2023 12:17:47 -0700 Subject: [PATCH 6/6] Update user's search attribute using database filter Using a database filter is the cleanest approach because it ensures the logic is applied whenever the user is updated regardless of whichever endpoint was used. --- app/config/collections.php | 2 +- app/controllers/api/account.php | 12 +++--------- app/controllers/api/users.php | 16 ++++------------ app/controllers/shared/api.php | 19 ------------------- app/init.php | 23 +++++++++++++++++++++++ 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index c7572ddad7..b677696696 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1407,7 +1407,7 @@ $collections = [ 'required' => false, 'default' => null, 'array' => false, - 'filters' => [], + 'filters' => ['userSearch'], ] ], 'indexes' => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 02d70f24e9..296d5329d5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1529,11 +1529,9 @@ App::patch('/v1/account/name') ->inject('user') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { $user->setAttribute('name', $name); - $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); @@ -1628,8 +1626,7 @@ App::patch('/v1/account/email') ->inject('user') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -1646,7 +1643,6 @@ App::patch('/v1/account/email') ->setAttribute('email', $email) ->setAttribute('emailVerification', false) // After this user needs to confirm mail again ; - $user->setAttribute('search', $prepareUserSearch($user)); if (empty($passwordUpdate)) { $user @@ -1692,8 +1688,7 @@ App::patch('/v1/account/phone') ->inject('user') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -1708,7 +1703,6 @@ App::patch('/v1/account/phone') ->setAttribute('phone', $phone) ->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again ; - $user->setAttribute('search', $prepareUserSearch($user)); if (empty($passwordUpdate)) { $user diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index dd1c649567..5c9fd130b7 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -686,8 +686,7 @@ App::put('/v1/users/:userId/labels') ->inject('response') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $userId, array $labels, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $userId, array $labels, Response $response, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -696,7 +695,6 @@ App::put('/v1/users/:userId/labels') } $user->setAttribute('labels', (array) \array_values(\array_unique($labels))); - $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -799,8 +797,7 @@ App::patch('/v1/users/:userId/name') ->inject('response') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $userId, string $name, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $userId, string $name, Response $response, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -809,7 +806,6 @@ App::patch('/v1/users/:userId/name') } $user->setAttribute('name', $name); - $user->setAttribute('search', $prepareUserSearch($user)); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -898,8 +894,7 @@ App::patch('/v1/users/:userId/email') ->inject('response') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $userId, string $email, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $userId, string $email, Response $response, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -914,7 +909,6 @@ App::patch('/v1/users/:userId/email') ->setAttribute('emailVerification', false) ; - $user->setAttribute('search', $prepareUserSearch($user)); try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -947,8 +941,7 @@ App::patch('/v1/users/:userId/phone') ->inject('response') ->inject('dbForProject') ->inject('events') - ->inject('prepareUserSearch') - ->action(function (string $userId, string $number, Response $response, Database $dbForProject, Event $events, callable $prepareUserSearch) { + ->action(function (string $userId, string $number, Response $response, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -960,7 +953,6 @@ App::patch('/v1/users/:userId/phone') ->setAttribute('phone', $number) ->setAttribute('phoneVerification', false) ; - $user->setAttribute('search', $prepareUserSearch($user)); try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index fcc12a3a2a..93cf81e1fa 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -87,25 +87,6 @@ $databaseListener = function (string $event, Document $document, Stats $usage) { } }; -App::setResource('prepareUserSearch', function () { - return function (Document $user): string { - $searchValues = [ - $user->getId(), - $user->getAttribute('email', ''), - $user->getAttribute('name', ''), - $user->getAttribute('phone', '') - ]; - - foreach ($user->getAttribute('labels', []) as $label) { - $searchValues[] = 'label:' . $label; - } - - $search = implode(' ', \array_filter($searchValues)); - - return $search; - }; -}); - App::init() ->groups(['api']) ->inject('utopia') diff --git a/app/init.php b/app/init.php index b5def038c1..3d3a46ef91 100644 --- a/app/init.php +++ b/app/init.php @@ -457,6 +457,29 @@ Database::addFilter( } ); +Database::addFilter( + 'userSearch', + function (mixed $value, Document $user) { + $searchValues = [ + $user->getId(), + $user->getAttribute('email', ''), + $user->getAttribute('name', ''), + $user->getAttribute('phone', '') + ]; + + foreach ($user->getAttribute('labels', []) as $label) { + $searchValues[] = 'label:' . $label; + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + /** * DB Formats */