From f77eb9a23fad9e56c3154dd6b774cd3767224062 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 1 Sep 2025 21:45:18 +0530 Subject: [PATCH 01/17] added new internal attributes to the adapters --- src/Database/Adapter.php | 2 ++ src/Database/Adapter/MariaDB.php | 11 ++++++++++- src/Database/Adapter/Pool.php | 2 +- src/Database/Adapter/Postgres.php | 13 +++++++++++-- src/Database/Adapter/SQL.php | 28 ++++++++++++++++++++++++++-- src/Database/Adapter/SQLite.php | 2 ++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 4e2990e70..f5a7fd651 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1233,6 +1233,7 @@ protected function escapeWildcards(string $value): string * @param string $attribute * @param int|float $value * @param string $updatedAt + * @param string|null $updatedBy * @param int|float|null $min * @param int|float|null $max * @return bool @@ -1244,6 +1245,7 @@ abstract public function increaseDocumentAttribute( string $attribute, int|float $value, string $updatedAt, + ?string $updatedBy, int|float|null $min = null, int|float|null $max = null ): bool; diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index c78d6637c..84d720823 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -159,6 +159,8 @@ public function createCollection(string $name, array $attributes = [], array $in _uid VARCHAR(255) NOT NULL, _createdAt DATETIME(3) DEFAULT NULL, _updatedAt DATETIME(3) DEFAULT NULL, + _createdBy VARCHAR(255) DEFAULT NULL, + _updatedBy VARCHAR(255) DEFAULT NULL, _permissions MEDIUMTEXT DEFAULT NULL, PRIMARY KEY (_id), " . \implode(' ', $attributeStrings) . " @@ -827,6 +829,8 @@ public function createDocument(Document $collection, Document $document): Docume $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = \json_encode($document->getPermissions()); if ($this->sharedTables) { @@ -953,6 +957,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = json_encode($document->getPermissions()); $name = $this->filter($collection); @@ -1241,6 +1246,7 @@ public function getUpsertStatement( * @param string $attribute * @param int|float $value * @param string $updatedAt + * @param string|null $updatedBy * @param int|float|null $min * @param int|float|null $max * @return bool @@ -1252,6 +1258,7 @@ public function increaseDocumentAttribute( string $attribute, int|float $value, string $updatedAt, + ?string $updatedBy, int|float|null $min = null, int|float|null $max = null ): bool { @@ -1265,7 +1272,8 @@ public function increaseDocumentAttribute( UPDATE {$this->getSQLTable($name)} SET `{$attribute}` = `{$attribute}` + :val, - `_updatedAt` = :updatedAt + `_updatedAt` = :updatedAt, + `_updatedBy` = :updatedBy WHERE _uid = :_uid {$this->getTenantQuery($collection)} "; @@ -1278,6 +1286,7 @@ public function increaseDocumentAttribute( $stmt->bindValue(':_uid', $id); $stmt->bindValue(':val', $value); $stmt->bindValue(':updatedAt', $updatedAt); + $stmt->bindValue(':updatedBy', $updatedBy); if ($this->sharedTables) { $stmt->bindValue(':_tenant', $this->tenant); diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index e4d85f9e8..c9c513ce1 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -481,7 +481,7 @@ protected function getAttributeProjection(array $selections, string $prefix, arr return $this->delegate(__FUNCTION__, \func_get_args()); } - public function increaseDocumentAttribute(string $collection, string $id, string $attribute, float|int $value, string $updatedAt, float|int|null $min = null, float|int|null $max = null): bool + public function increaseDocumentAttribute(string $collection, string $id, string $attribute, float|int $value, string $updatedAt, ?string $updatedBy, float|int|null $min = null, float|int|null $max = null): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index f666d1184..8536cc03f 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -237,6 +237,8 @@ public function createCollection(string $name, array $attributes = [], array $in " . $sqlTenant . " \"_createdAt\" TIMESTAMP(3) DEFAULT NULL, \"_updatedAt\" TIMESTAMP(3) DEFAULT NULL, + \"_createdBy\" VARCHAR(255) DEFAULT NULL, + \"_updatedBy\" VARCHAR(255) DEFAULT NULL, _permissions TEXT DEFAULT NULL, " . \implode(' ', $attributeStrings) . " PRIMARY KEY (_id) @@ -963,6 +965,8 @@ public function createDocument(Document $collection, Document $document): Docume $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = \json_encode($document->getPermissions()); if ($this->sharedTables) { @@ -1077,6 +1081,8 @@ public function updateDocument(Document $collection, string $id, Document $docum $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = json_encode($document->getPermissions()); $name = $this->filter($collection); @@ -1345,12 +1351,13 @@ protected function getUpsertStatement( * @param string $attribute * @param int|float $value * @param string $updatedAt + * @param string|null $updatedBy * @param int|float|null $min * @param int|float|null $max * @return bool * @throws DatabaseException */ - public function increaseDocumentAttribute(string $collection, string $id, string $attribute, int|float $value, string $updatedAt, int|float|null $min = null, int|float|null $max = null): bool + public function increaseDocumentAttribute(string $collection, string $id, string $attribute, int|float $value, string $updatedAt, ?string $updatedBy, int|float|null $min = null, int|float|null $max = null): bool { $name = $this->filter($collection); $attribute = $this->filter($attribute); @@ -1362,7 +1369,8 @@ public function increaseDocumentAttribute(string $collection, string $id, string UPDATE {$this->getSQLTable($name)} SET \"{$attribute}\" = \"{$attribute}\" + :val, - \"_updatedAt\" = :updatedAt + \"_updatedAt\" = :updatedAt, + \"_updatedBy\" = :updatedBy WHERE _uid = :_uid {$this->getTenantQuery($collection)} "; @@ -1375,6 +1383,7 @@ public function increaseDocumentAttribute(string $collection, string $id, string $stmt->bindValue(':_uid', $id); $stmt->bindValue(':val', $value); $stmt->bindValue(':updatedAt', $updatedAt); + $stmt->bindValue(':updatedBy', $updatedBy); if ($this->sharedTables) { $stmt->bindValue(':_tenant', $this->tenant); diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index c51d525c9..e4778bc37 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -409,6 +409,14 @@ public function getDocument(Document $collection, string $id, array $queries = [ $document['$updatedAt'] = $document['_updatedAt']; unset($document['_updatedAt']); } + if (\array_key_exists('_createdBy', $document)) { + $document['$createdBy'] = $document['_createdBy']; + unset($document['_createdBy']); + } + if (\array_key_exists('_updatedBy', $document)) { + $document['$updatedBy'] = $document['_updatedBy']; + unset($document['_updatedBy']); + } if (\array_key_exists('_permissions', $document)) { $document['$permissions'] = json_decode($document['_permissions'] ?? '[]', true); unset($document['_permissions']); @@ -469,6 +477,14 @@ public function updateDocuments(Document $collection, Document $updates, array $ $attributes['_createdAt'] = $updates->getCreatedAt(); } + if (!empty($updates->getCreatedBy())) { + $attributes['_createdBy'] = $updates->getCreatedBy(); + } + + if (!empty($updates->getUpdatedBy())) { + $attributes['_updatedBy'] = $updates->getUpdatedBy(); + } + if ($updates->offsetExists('$permissions')) { $attributes['_permissions'] = json_encode($updates->getPermissions()); } @@ -1054,10 +1070,12 @@ public function getAttributeWidth(Document $collection): int * `_tenant` int => 4 bytes * `_createdAt` datetime(3) => 7 bytes * `_updatedAt` datetime(3) => 7 bytes + * `_createdBy` varchar(255) => 1021 (4 * 255 + 1) bytes + * `_updatedBy` varchar(255) => 1021 (4 * 255 + 1) bytes * `_permissions` mediumtext => 20 */ - $total = 1067; + $total = 3109; $attributes = $collection->getAttributes()['attributes']; @@ -1891,7 +1909,7 @@ protected function getAttributeProjection(array $selections, string $prefix, arr $projections = []; $projections[] = "{$this->quote($prefix)}.*"; - $internalColumns = ['_id', '_uid', '_createdAt', '_updatedAt', '_permissions']; + $internalColumns = ['_id', '_uid', '_createdAt', '_updatedAt', '_permissions', '_createdBy', '_updatedBy']; if ($this->sharedTables) { $internalColumns[] = '_tenant'; } @@ -1950,6 +1968,8 @@ protected function getInternalKeyForAttribute(string $attribute): string '$tenant' => '_tenant', '$createdAt' => '_createdAt', '$updatedAt' => '_updatedAt', + '$createdBy' => '_createdBy', + '$updatedBy' => '_updatedBy', '$permissions' => '_permissions', default => $attribute }; @@ -2044,6 +2064,8 @@ public function createDocuments(Document $collection, array $documents): array $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); $attributes['_permissions'] = \json_encode($document->getPermissions()); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); if (!empty($document->getSequence())) { $attributes['_id'] = $document->getSequence(); @@ -2158,6 +2180,8 @@ public function createOrUpdateDocuments( $attributes['_uid'] = $document->getId(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = \json_encode($document->getPermissions()); if (!empty($document->getSequence())) { diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index ff0246265..b7c4b70f0 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -168,6 +168,8 @@ public function createCollection(string $name, array $attributes = [], array $in {$tenantQuery} `_createdAt` DATETIME(3) DEFAULT NULL, `_updatedAt` DATETIME(3) DEFAULT NULL, + `_createdBy` VARCHAR(255) DEFAULT NULL, + `_updatedBy` VARCHAR(255) DEFAULT NULL, `_permissions` MEDIUMTEXT DEFAULT NULL".(!empty($attributes) ? ',' : '')." " . \substr(\implode(' ', $attributeStrings), 0, -2) . " ) From 484d045aa0db8d46bcac31099fb7a3bdee551203 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 1 Sep 2025 21:45:37 +0530 Subject: [PATCH 02/17] added get user method to the Authorization --- src/Database/Validator/Authorization.php | 24 ++++++++++++++++++++++ tests/unit/Validator/AuthorizationTest.php | 20 ++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index 2dd645ba7..c29066396 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator; +use Utopia\Database\Helpers\Role; use Utopia\Validator; class Authorization extends Validator @@ -23,6 +24,11 @@ class Authorization extends Validator */ protected string $message = 'Authorization Error'; + /** + * @var string|null + */ + protected static ?string $user = null; + /** * @param string $action */ @@ -82,6 +88,10 @@ public function isValid($permissions): bool */ public static function setRole(string $role): void { + $userIdetifier = Role::parse($role)->getIdentifier(); + if ($userIdetifier) { + self::$user = $userIdetifier; + } self::$roles[$role] = true; } @@ -92,9 +102,23 @@ public static function setRole(string $role): void */ public static function unsetRole(string $role): void { + $userIdetifier = Role::parse($role)->getIdentifier(); + if ($userIdetifier && self::$user == $userIdetifier) { + self::$user = null; + } unset(self::$roles[$role]); } + /** + * Get current user + * + * @return string|null + */ + public static function getUser(): string|null + { + return self::$user; + } + /** * @return array */ diff --git a/tests/unit/Validator/AuthorizationTest.php b/tests/unit/Validator/AuthorizationTest.php index 08c1e46f2..883861367 100644 --- a/tests/unit/Validator/AuthorizationTest.php +++ b/tests/unit/Validator/AuthorizationTest.php @@ -119,4 +119,24 @@ public function testNestedSkips(): void $this->assertEquals(true, Authorization::$status); } + + public function testSetUserFromRoles(): void + { + $currentUserRole = Role::user("123"); + Authorization::setRole(Role::user("123")->toString()); + $this->assertEquals($currentUserRole->getIdentifier(), Authorization::getUser()); + + $roles = []; + $roles[] = Role::user("123"); + $roles[] = Role::user("123", 'verififed'); + $roles[] = Role::user("126", 'unverififed'); + $roles[] = Role::any(); + $roles[] = Role::users(); + + foreach ($roles as $role) { + Authorization::setRole($role->toString()); + } + $expectedRole = Role::user("126", 'unverififed'); + $this->assertEquals($expectedRole->getIdentifier(), Authorization::getUser()); + } } From 1bf9fb820b73c8127e24146b87deda63e8391d62 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 1 Sep 2025 21:45:53 +0530 Subject: [PATCH 03/17] added helper methods and validators --- src/Database/Document.php | 16 ++++++++++++++++ src/Database/Validator/Structure.php | 24 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index e8a7a3a08..1fed790de 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -164,6 +164,14 @@ public function getCreatedAt(): ?string return $this->getAttribute('$createdAt'); } + /** + * @return string|null + */ + public function getCreatedBy(): ?string + { + return $this->getAttribute('$createdBy'); + } + /** * @return string|null */ @@ -172,6 +180,14 @@ public function getUpdatedAt(): ?string return $this->getAttribute('$updatedAt'); } + /** + * @return string|null + */ + public function getUpdatedBy(): ?string + { + return $this->getAttribute('$updatedBy'); + } + /** * @return int|null */ diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index cfb12fa3a..79627d409 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -84,7 +84,29 @@ class Structure extends Validator 'signed' => false, 'array' => false, 'filters' => [], - ] + ], + [ + '$id' => '$createdBy', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => '$updatedBy', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], ]; /** From 28a979e31c352fd2a7322e26b576441beacfbe1c Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 1 Sep 2025 21:46:08 +0530 Subject: [PATCH 04/17] updated existing methods and tests --- src/Database/Database.php | 63 +++++++- tests/e2e/Adapter/Scopes/DocumentTests.php | 176 +++++++++++++++++++++ 2 files changed, 236 insertions(+), 3 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 72fd8574c..1db2e3866 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -230,6 +230,28 @@ class Database 'array' => false, 'filters' => ['datetime'] ], + [ + '$id' => '$createdBy', + 'type' => self::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => '$updatedBy', + 'type' => self::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], [ '$id' => '$permissions', 'type' => Database::VAR_STRING, @@ -246,6 +268,8 @@ class Database '_uid', '_createdAt', '_updatedAt', + '_createdBy', + '_updatedBy', '_permissions', ]; @@ -254,6 +278,8 @@ class Database '_uid', '_createdAt', '_updatedAt', + '_createdBy', + '_updatedBy', '_permissions_id', '_permissions', ]; @@ -3863,12 +3889,16 @@ public function createDocument(string $collection, Document $document): Document $createdAt = $document->getCreatedAt(); $updatedAt = $document->getUpdatedAt(); + $createdBy = $document->getCreatedBy(); + $updatedBy = $document->getUpdatedBy(); $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) ->setAttribute('$createdAt', ($createdAt === null || !$this->preserveDates) ? $time : $createdAt) - ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt); + ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt) + ->setAttribute('$createdBy', value: $createdBy === null ? Authorization::getUser() : $createdBy) + ->setAttribute('$updatedBy', $updatedBy === null ? Authorization::getUser() : $updatedBy); if (empty($document->getPermissions())) { $document->setAttribute('$permissions', []); @@ -3967,12 +3997,17 @@ public function createDocuments( foreach ($documents as $document) { $createdAt = $document->getCreatedAt(); $updatedAt = $document->getUpdatedAt(); + $createdBy = $document->getCreatedBy(); + $updatedBy = $document->getUpdatedBy(); $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) ->setAttribute('$createdAt', ($createdAt === null || !$this->preserveDates) ? $time : $createdAt) - ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt); + ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt) + ->setAttribute('$createdBy', $createdBy === null ? Authorization::getUser() : $createdBy) + ->setAttribute('$updatedBy', $updatedBy === null ? Authorization::getUser() : $updatedBy); + if (empty($document->getPermissions())) { $document->setAttribute('$permissions', []); @@ -4396,10 +4431,12 @@ public function updateDocument(string $collection, string $id, Document $documen $skipPermissionsUpdate = ($originalPermissions === $currentPermissions); } $createdAt = $document->getCreatedAt(); + $createdBy = $document->getCreatedBy(); $document = \array_merge($old->getArrayCopy(), $document->getArrayCopy()); $document['$collection'] = $old->getAttribute('$collection'); // Make sure user doesn't switch collection ID $document['$createdAt'] = ($createdAt === null || !$this->preserveDates) ? $old->getCreatedAt() : $createdAt; + $document['$createdBy'] = $createdBy === null ? $old->getCreatedBy() : $createdBy; if ($this->adapter->getSharedTables()) { $document['$tenant'] = $old->getTenant(); // Make sure user doesn't switch tenant @@ -4524,6 +4561,7 @@ public function updateDocument(string $collection, string $id, Document $documen if ($shouldUpdate) { $document->setAttribute('$updatedAt', ($newUpdatedAt === null || !$this->preserveDates) ? $time : $newUpdatedAt); + $document->setAttribute('$updatedBy', Authorization::getUser()); } // Check if document was updated after the request timestamp @@ -4653,12 +4691,20 @@ public function updateDocuments( $updates['$createdAt'] = $updates->getCreatedAt(); } + if ($updates->getCreatedBy() == null) { + unset($updates['$createdBy']); + } else { + $updates['$createdBy'] = $updates->getCreatedBy(); + } + if ($this->adapter->getSharedTables()) { $updates['$tenant'] = $this->adapter->getTenant(); } $updatedAt = $updates->getUpdatedAt(); + $updatedBy = $updates->getUpdatedBy(); $updates['$updatedAt'] = ($updatedAt === null || !$this->preserveDates) ? DateTime::now() : $updatedAt; + $updates['$updatedBy'] = $updatedBy === null ? Authorization::getUser() : $updatedBy; $updates = $this->encode($collection, $updates); // Check new document structure @@ -5289,11 +5335,12 @@ public function createOrUpdateDocumentsWithIncrease( } $updatedAt = $document->getUpdatedAt(); - + $updatedBy = $document->getUpdatedBy(); $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt) + ->setAttribute('$updatedBy', $updatedBy === null ? Authorization::getUser() : $updatedBy) ->removeAttribute('$sequence'); $createdAt = $document->getCreatedAt(); @@ -5303,6 +5350,12 @@ public function createOrUpdateDocumentsWithIncrease( $document->setAttribute('$createdAt', $createdAt); } + $createdBy = $document->getCreatedBy(); + if ($createdBy === null) { + $document->setAttribute('$createdBy', $old->isEmpty() ? Authorization::getUser() : $old->getCreatedBy()); + } else { + $document->setAttribute('$createdBy', $createdBy); + } // Force matching optional parameter sets // Doesn't use decode as that intentionally skips null defaults to reduce payload size foreach ($collectionAttributes as $attr) { @@ -5497,6 +5550,7 @@ public function increaseDocumentAttribute( $time = DateTime::now(); $updatedAt = $document->getUpdatedAt(); $updatedAt = (empty($updatedAt) || !$this->preserveDates) ? $time : $updatedAt; + $updatedBy = $document->getUpdatedBy(); $max = $max ? $max - $value : null; $this->adapter->increaseDocumentAttribute( @@ -5505,6 +5559,7 @@ public function increaseDocumentAttribute( $attribute, $value, $updatedAt, + $updatedBy, max: $max ); @@ -5596,6 +5651,7 @@ public function decreaseDocumentAttribute( $time = DateTime::now(); $updatedAt = $document->getUpdatedAt(); $updatedAt = (empty($updatedAt) || !$this->preserveDates) ? $time : $updatedAt; + $updatedBy = $document->getUpdatedBy() == null ? Authorization::getUser() : $document->getUpdatedBy(); $min = $min ? $min + $value : null; $this->adapter->increaseDocumentAttribute( @@ -5604,6 +5660,7 @@ public function decreaseDocumentAttribute( $attribute, $value * -1, $updatedAt, + $updatedBy, min: $min ); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 79e2d8299..d7be45999 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5995,4 +5995,180 @@ public function testCreateUpdateDocumentsMismatch(): void } $database->deleteCollection($colName); } + + public function testCreatedByUpdatedBy(): void + { + $collection = 'test_created_updated_by'; + $database = static::getDatabase(); + + + $database->createCollection($collection); + $database->createAttribute($collection, 'string', Database::VAR_STRING, 128, false); + $database->createAttribute($collection, 'number', Database::VAR_INTEGER, 0, false); + + + Authorization::setRole('user:test_user_1'); + $doc1 = $database->createDocument($collection, new Document([ + '$id' => 'test1', + 'string' => 'test1', + 'number' => 100, + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ])); + + + $this->assertEquals('test_user_1', $doc1->getCreatedBy()); + $this->assertEquals('test_user_1', $doc1->getUpdatedBy()); + + + $retrievedDoc1 = $database->getDocument($collection, 'test1'); + $this->assertEquals('test_user_1', $retrievedDoc1->getCreatedBy()); + $this->assertEquals('test_user_1', $retrievedDoc1->getUpdatedBy()); + + + $doc2 = $database->createDocument($collection, new Document([ + '$id' => 'test2', + 'string' => 'test2', + 'number' => 200, + '$createdBy' => 'explicit_creator', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ])); + + + $this->assertEquals('explicit_creator', $doc2->getCreatedBy()); + $this->assertEquals('test_user_1', $doc2->getUpdatedBy()); + + + $retrievedDoc2 = $database->getDocument($collection, 'test2'); + $this->assertEquals('explicit_creator', $retrievedDoc2->getCreatedBy()); + $this->assertEquals('test_user_1', $retrievedDoc2->getUpdatedBy()); + + + $doc3 = $database->createDocument($collection, new Document([ + '$id' => 'test3', + 'string' => 'test3', + 'number' => 300, + '$updatedBy' => 'explicit_updater', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ])); + + + $this->assertEquals('test_user_1', $doc3->getCreatedBy()); + $this->assertEquals('explicit_updater', $doc3->getUpdatedBy()); + + + $retrievedDoc3 = $database->getDocument($collection, 'test3'); + $this->assertEquals('test_user_1', $retrievedDoc3->getCreatedBy()); + $this->assertEquals('explicit_updater', $retrievedDoc3->getUpdatedBy()); + + + $doc4 = $database->createDocument($collection, new Document([ + '$id' => 'test4', + 'string' => 'test4', + 'number' => 400, + '$createdBy' => 'explicit_creator_2', + '$updatedBy' => 'explicit_updater_2', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ])); + + + $this->assertEquals('explicit_creator_2', $doc4->getCreatedBy()); + $this->assertEquals('explicit_updater_2', $doc4->getUpdatedBy()); + + + $retrievedDoc4 = $database->getDocument($collection, 'test4'); + $this->assertEquals('explicit_creator_2', $retrievedDoc4->getCreatedBy()); + $this->assertEquals('explicit_updater_2', $retrievedDoc4->getUpdatedBy()); + + + $allDocs = $database->find($collection); + $this->assertCount(4, $allDocs); + + + Authorization::setRole('user:test_user_2'); + $updateDoc = new Document([ + 'string' => 'updated_test1', + 'number' => 150 + ]); + $updatedDoc = $database->updateDocument($collection, 'test1', $updateDoc); + + // Verify createdBy is preserved, updatedBy changes to current user + $this->assertEquals('test_user_1', $updatedDoc->getCreatedBy()); // Should preserve original creator + $this->assertEquals('test_user_2', $updatedDoc->getUpdatedBy()); // Should update to current user + + $retrievedUpdatedDoc = $database->getDocument($collection, 'test1'); + $this->assertEquals('test_user_1', $retrievedUpdatedDoc->getCreatedBy()); + $this->assertEquals('test_user_2', $retrievedUpdatedDoc->getUpdatedBy()); + + $upsertDoc = new Document([ + '$id' => 'test2', + 'string' => 'upserted_test2', + 'number' => 250 + ]); + $upsertCount = $database->createOrUpdateDocuments($collection, [$upsertDoc]); + $this->assertEquals(1, $upsertCount); + + $upsertedDoc = $database->getDocument($collection, 'test2'); + $this->assertEquals('explicit_creator', $upsertedDoc->getCreatedBy()); // Should preserve original creator + $this->assertEquals('test_user_2', $upsertedDoc->getUpdatedBy()); // Should update to current user + + $upsertNewDoc = new Document([ + '$id' => 'test5', + 'string' => 'new_test5', + 'number' => 500, + '$createdBy' => 'new_creator', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]); + $upsertNewCount = $database->createOrUpdateDocuments($collection, [$upsertNewDoc]); + $this->assertEquals(1, $upsertNewCount); + + $newUpsertedDoc = $database->getDocument($collection, 'test5'); + $this->assertEquals('new_creator', $newUpsertedDoc->getCreatedBy()); // Should use explicit creator + $this->assertEquals('test_user_2', $newUpsertedDoc->getUpdatedBy()); // Should use current user + + + $bulkDocs = [ + new Document([ + '$id' => 'bulk1', + 'string' => 'bulk1', + 'number' => 600, + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]), + new Document([ + '$id' => 'bulk2', + 'string' => 'bulk2', + 'number' => 700, + '$createdBy' => 'bulk_creator', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]), + new Document([ + '$id' => 'bulk3', + 'string' => 'bulk3', + 'number' => 800, + '$updatedBy' => 'bulk_updater', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]) + ]; + $bulkCount = $database->createDocuments($collection, $bulkDocs); + $this->assertEquals(3, $bulkCount); + + $bulkDoc1 = $database->getDocument($collection, 'bulk1'); + $bulkDoc2 = $database->getDocument($collection, 'bulk2'); + $bulkDoc3 = $database->getDocument($collection, 'bulk3'); + + $this->assertEquals('test_user_2', $bulkDoc1->getCreatedBy()); + $this->assertEquals('test_user_2', $bulkDoc1->getUpdatedBy()); + + $this->assertEquals('bulk_creator', $bulkDoc2->getCreatedBy()); + $this->assertEquals('test_user_2', $bulkDoc2->getUpdatedBy()); + + $this->assertEquals('test_user_2', $bulkDoc3->getCreatedBy()); + $this->assertEquals('bulk_updater', $bulkDoc3->getUpdatedBy()); + + $finalAllDocs = $database->find($collection); + $this->assertCount(8, $finalAllDocs); + + $database->deleteCollection($collection); + } + + } From 8a856669893d46c7c936fe8bd39fcb04c4b8a51b Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 2 Sep 2025 13:28:38 +0530 Subject: [PATCH 05/17] updated attribute limit tests --- tests/e2e/Adapter/Scopes/AttributeTests.php | 4 ++-- tests/e2e/Adapter/Scopes/CollectionTests.php | 2 +- tests/e2e/Adapter/Scopes/GeneralTests.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index 89ab81a50..de8b9c2bc 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -803,7 +803,7 @@ public function testWidthLimit(): void $collection = $database->createCollection('width_limit'); $init = $database->getAdapter()->getAttributeWidth($collection); - $this->assertEquals(1067, $init); + $this->assertEquals(3109, $init); $attribute = new Document([ '$id' => ID::custom('varchar_100'), @@ -957,7 +957,7 @@ public function testExceptionWidthLimit(): void $attributes[] = new Document([ '$id' => ID::custom('varchar_16000'), 'type' => Database::VAR_STRING, - 'size' => 16000, + 'size' => 15500, 'required' => true, 'default' => null, 'signed' => true, diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 5178a414d..5a1b753c5 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -607,7 +607,7 @@ public function testRowSizeToLarge(): void $collection_1 = $database->createCollection('row_size_1'); $collection_2 = $database->createCollection('row_size_2'); - $this->assertEquals(true, $database->createAttribute($collection_1->getId(), 'attr_1', Database::VAR_STRING, 16000, true)); + $this->assertEquals(true, $database->createAttribute($collection_1->getId(), 'attr_1', Database::VAR_STRING, 15500, true)); try { $database->createAttribute($collection_1->getId(), 'attr_2', Database::VAR_STRING, Database::LENGTH_KEY, true); diff --git a/tests/e2e/Adapter/Scopes/GeneralTests.php b/tests/e2e/Adapter/Scopes/GeneralTests.php index 795f4d096..768c6599e 100644 --- a/tests/e2e/Adapter/Scopes/GeneralTests.php +++ b/tests/e2e/Adapter/Scopes/GeneralTests.php @@ -295,7 +295,7 @@ public function testGetAttributeLimit(): void } public function testGetIndexLimit(): void { - $this->assertEquals(58, $this->getDatabase()->getLimitForIndexes()); + $this->assertEquals(56, $this->getDatabase()->getLimitForIndexes()); } public function testGetId(): void From a6789e37751b6994a7fd85a38b2a665046d3d908 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 2 Sep 2025 13:30:27 +0530 Subject: [PATCH 06/17] added null checker for the createdBy --- src/Database/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1db2e3866..3b5b5fc0e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4691,7 +4691,7 @@ public function updateDocuments( $updates['$createdAt'] = $updates->getCreatedAt(); } - if ($updates->getCreatedBy() == null) { + if ($updates->getCreatedBy() === null) { unset($updates['$createdBy']); } else { $updates['$createdBy'] = $updates->getCreatedBy(); From 2a4805ed520b9fe8d0e244398fd46f3e941a29d8 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 2 Sep 2025 13:38:23 +0530 Subject: [PATCH 07/17] updated unsetting of createdby and updatedby in the find --- src/Database/Adapter/SQL.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index e4778bc37..3349c1c7f 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -2560,6 +2560,14 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25 $results[$index]['$updatedAt'] = $document['_updatedAt']; unset($results[$index]['_updatedAt']); } + if (\array_key_exists('_createdBy', $document)) { + $results[$index]['$createdBy'] = $document['_createdBy']; + unset($results[$index]['_createdBy']); + } + if (\array_key_exists('_updatedBy', $document)) { + $results[$index]['$updatedBy'] = $document['_updatedBy']; + unset($results[$index]['_updatedBy']); + } if (\array_key_exists('_permissions', $document)) { $results[$index]['$permissions'] = \json_decode($document['_permissions'] ?? '[]', true); unset($results[$index]['_permissions']); From 67791d1b3162bc25b7ee268407c514fe579d6d3f Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 2 Sep 2025 13:47:23 +0530 Subject: [PATCH 08/17] updated sqlite adapter --- src/Database/Adapter/SQLite.php | 4 + tests/e2e/Adapter/Scopes/DocumentTests.php | 115 +++++++++++---------- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index b7c4b70f0..d3ff4315c 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -527,6 +527,8 @@ public function createDocument(Document $collection, Document $document): Docume $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = json_encode($document->getPermissions()); if ($this->sharedTables) { @@ -649,6 +651,8 @@ public function updateDocument(Document $collection, string $id, Document $docum $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); $attributes['_updatedAt'] = $document->getUpdatedAt(); + $attributes['_createdBy'] = $document->getCreatedBy(); + $attributes['_updatedBy'] = $document->getUpdatedBy(); $attributes['_permissions'] = json_encode($document->getPermissions()); if ($this->sharedTables) { diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index d7be45999..6481cc166 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -6104,71 +6104,72 @@ public function testCreatedByUpdatedBy(): void 'string' => 'upserted_test2', 'number' => 250 ]); - $upsertCount = $database->createOrUpdateDocuments($collection, [$upsertDoc]); - $this->assertEquals(1, $upsertCount); - - $upsertedDoc = $database->getDocument($collection, 'test2'); - $this->assertEquals('explicit_creator', $upsertedDoc->getCreatedBy()); // Should preserve original creator - $this->assertEquals('test_user_2', $upsertedDoc->getUpdatedBy()); // Should update to current user - - $upsertNewDoc = new Document([ - '$id' => 'test5', - 'string' => 'new_test5', - 'number' => 500, - '$createdBy' => 'new_creator', - '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] - ]); - $upsertNewCount = $database->createOrUpdateDocuments($collection, [$upsertNewDoc]); - $this->assertEquals(1, $upsertNewCount); - - $newUpsertedDoc = $database->getDocument($collection, 'test5'); - $this->assertEquals('new_creator', $newUpsertedDoc->getCreatedBy()); // Should use explicit creator - $this->assertEquals('test_user_2', $newUpsertedDoc->getUpdatedBy()); // Should use current user + if ($database->getAdapter()->getSupportForUpserts()) { + $upsertCount = $database->createOrUpdateDocuments($collection, [$upsertDoc]); + $this->assertEquals(1, $upsertCount); + + $upsertedDoc = $database->getDocument($collection, 'test2'); + $this->assertEquals('explicit_creator', $upsertedDoc->getCreatedBy()); // Should preserve original creator + $this->assertEquals('test_user_2', $upsertedDoc->getUpdatedBy()); // Should update to current user + + $upsertNewDoc = new Document([ + '$id' => 'test5', + 'string' => 'new_test5', + 'number' => 500, + '$createdBy' => 'new_creator', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]); + $upsertNewCount = $database->createOrUpdateDocuments($collection, [$upsertNewDoc]); + $this->assertEquals(1, $upsertNewCount); - $bulkDocs = [ - new Document([ - '$id' => 'bulk1', - 'string' => 'bulk1', - 'number' => 600, - '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] - ]), - new Document([ - '$id' => 'bulk2', - 'string' => 'bulk2', - 'number' => 700, - '$createdBy' => 'bulk_creator', - '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] - ]), - new Document([ - '$id' => 'bulk3', - 'string' => 'bulk3', - 'number' => 800, - '$updatedBy' => 'bulk_updater', - '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] - ]) - ]; - $bulkCount = $database->createDocuments($collection, $bulkDocs); - $this->assertEquals(3, $bulkCount); + $newUpsertedDoc = $database->getDocument($collection, 'test5'); + $this->assertEquals('new_creator', $newUpsertedDoc->getCreatedBy()); // Should use explicit creator + $this->assertEquals('test_user_2', $newUpsertedDoc->getUpdatedBy()); // Should use current user - $bulkDoc1 = $database->getDocument($collection, 'bulk1'); - $bulkDoc2 = $database->getDocument($collection, 'bulk2'); - $bulkDoc3 = $database->getDocument($collection, 'bulk3'); - $this->assertEquals('test_user_2', $bulkDoc1->getCreatedBy()); - $this->assertEquals('test_user_2', $bulkDoc1->getUpdatedBy()); + $bulkDocs = [ + new Document([ + '$id' => 'bulk1', + 'string' => 'bulk1', + 'number' => 600, + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]), + new Document([ + '$id' => 'bulk2', + 'string' => 'bulk2', + 'number' => 700, + '$createdBy' => 'bulk_creator', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]), + new Document([ + '$id' => 'bulk3', + 'string' => 'bulk3', + 'number' => 800, + '$updatedBy' => 'bulk_updater', + '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] + ]) + ]; + $bulkCount = $database->createDocuments($collection, $bulkDocs); + $this->assertEquals(3, $bulkCount); - $this->assertEquals('bulk_creator', $bulkDoc2->getCreatedBy()); - $this->assertEquals('test_user_2', $bulkDoc2->getUpdatedBy()); + $bulkDoc1 = $database->getDocument($collection, 'bulk1'); + $bulkDoc2 = $database->getDocument($collection, 'bulk2'); + $bulkDoc3 = $database->getDocument($collection, 'bulk3'); - $this->assertEquals('test_user_2', $bulkDoc3->getCreatedBy()); - $this->assertEquals('bulk_updater', $bulkDoc3->getUpdatedBy()); + $this->assertEquals('test_user_2', $bulkDoc1->getCreatedBy()); + $this->assertEquals('test_user_2', $bulkDoc1->getUpdatedBy()); - $finalAllDocs = $database->find($collection); - $this->assertCount(8, $finalAllDocs); + $this->assertEquals('bulk_creator', $bulkDoc2->getCreatedBy()); + $this->assertEquals('test_user_2', $bulkDoc2->getUpdatedBy()); - $database->deleteCollection($collection); - } + $this->assertEquals('test_user_2', $bulkDoc3->getCreatedBy()); + $this->assertEquals('bulk_updater', $bulkDoc3->getUpdatedBy()); + $finalAllDocs = $database->find($collection); + $this->assertCount(8, $finalAllDocs); + $database->deleteCollection($collection); + } + } } From 6e40e7976f3f2819375914deae0f68f8511875b2 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 15 Sep 2025 13:03:32 +0530 Subject: [PATCH 09/17] * add indexes for createdBy and updatedBy fields in MariaDB and Postgres adapters * update size for createdBy and updatedBy attributes in Database and Structure validators * enhance Authorization tests with additional roles --- src/Database/Adapter/MariaDB.php | 6 +++++- src/Database/Adapter/Postgres.php | 4 ++++ src/Database/Database.php | 10 +++++----- src/Database/Validator/Authorization.php | 9 +++++---- src/Database/Validator/Structure.php | 4 ++-- tests/e2e/Adapter/Scopes/DocumentTests.php | 11 ----------- tests/unit/Validator/AuthorizationTest.php | 14 ++++++++++++++ 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 84d720823..adda3f9b8 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -173,13 +173,17 @@ public function createCollection(string $name, array $attributes = [], array $in UNIQUE KEY _uid (_uid, _tenant), KEY _created_at (_tenant, _createdAt), KEY _updated_at (_tenant, _updatedAt), + KEY _created_by (_tenant, _createdBy), + KEY _updated_by (_tenant, _updatedBy), KEY _tenant_id (_tenant, _id) "; } else { $collection .= " UNIQUE KEY _uid (_uid), KEY _created_at (_createdAt), - KEY _updated_at (_updatedAt) + KEY _updated_at (_updatedAt), + KEY _created_by (_createdBy), + KEY _updated_by (_updatedBy) "; } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 8536cc03f..59825dd42 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -250,6 +250,8 @@ public function createCollection(string $name, array $attributes = [], array $in CREATE UNIQUE INDEX \"{$namespace}_{$this->tenant}_{$id}_uid\" ON {$this->getSQLTable($id)} (\"_uid\", \"_tenant\"); CREATE INDEX \"{$namespace}_{$this->tenant}_{$id}_created\" ON {$this->getSQLTable($id)} (_tenant, \"_createdAt\"); CREATE INDEX \"{$namespace}_{$this->tenant}_{$id}_updated\" ON {$this->getSQLTable($id)} (_tenant, \"_updatedAt\"); + CREATE INDEX \"{$namespace}_{$this->tenant}_{$id}_created_by\" ON {$this->getSQLTable($id)} (_tenant, \"_createdBy\"); + CREATE INDEX \"{$namespace}_{$this->tenant}_{$id}_updated_by\" ON {$this->getSQLTable($id)} (_tenant, \"_updatedBy\"); CREATE INDEX \"{$namespace}_{$this->tenant}_{$id}_tenant_id\" ON {$this->getSQLTable($id)} (_tenant, _id); "; } else { @@ -257,6 +259,8 @@ public function createCollection(string $name, array $attributes = [], array $in CREATE UNIQUE INDEX \"{$namespace}_{$id}_uid\" ON {$this->getSQLTable($id)} (\"_uid\"); CREATE INDEX \"{$namespace}_{$id}_created\" ON {$this->getSQLTable($id)} (\"_createdAt\"); CREATE INDEX \"{$namespace}_{$id}_updated\" ON {$this->getSQLTable($id)} (\"_updatedAt\"); + CREATE INDEX \"{$namespace}_{$id}_created_by\" ON {$this->getSQLTable($id)} (\"_createdBy\"); + CREATE INDEX \"{$namespace}_{$id}_updated_by\" ON {$this->getSQLTable($id)} (\"_updatedBy\"); "; } diff --git a/src/Database/Database.php b/src/Database/Database.php index 3b5b5fc0e..fc193bb4b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -234,7 +234,7 @@ class Database '$id' => '$createdBy', 'type' => self::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => Database::LENGTH_KEY, 'signed' => false, 'required' => false, 'default' => null, @@ -245,7 +245,7 @@ class Database '$id' => '$updatedBy', 'type' => self::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => Database::LENGTH_KEY, 'signed' => false, 'required' => false, 'default' => null, @@ -3897,7 +3897,7 @@ public function createDocument(string $collection, Document $document): Document ->setAttribute('$collection', $collection->getId()) ->setAttribute('$createdAt', ($createdAt === null || !$this->preserveDates) ? $time : $createdAt) ->setAttribute('$updatedAt', ($updatedAt === null || !$this->preserveDates) ? $time : $updatedAt) - ->setAttribute('$createdBy', value: $createdBy === null ? Authorization::getUser() : $createdBy) + ->setAttribute('$createdBy', $createdBy === null ? Authorization::getUser() : $createdBy) ->setAttribute('$updatedBy', $updatedBy === null ? Authorization::getUser() : $updatedBy); if (empty($document->getPermissions())) { @@ -5550,7 +5550,7 @@ public function increaseDocumentAttribute( $time = DateTime::now(); $updatedAt = $document->getUpdatedAt(); $updatedAt = (empty($updatedAt) || !$this->preserveDates) ? $time : $updatedAt; - $updatedBy = $document->getUpdatedBy(); + $updatedBy = Authorization::getUser(); $max = $max ? $max - $value : null; $this->adapter->increaseDocumentAttribute( @@ -5651,7 +5651,7 @@ public function decreaseDocumentAttribute( $time = DateTime::now(); $updatedAt = $document->getUpdatedAt(); $updatedAt = (empty($updatedAt) || !$this->preserveDates) ? $time : $updatedAt; - $updatedBy = $document->getUpdatedBy() == null ? Authorization::getUser() : $document->getUpdatedBy(); + $updatedBy = Authorization::getUser(); $min = $min ? $min + $value : null; $this->adapter->increaseDocumentAttribute( diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index c29066396..3347b2a3d 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -88,8 +88,9 @@ public function isValid($permissions): bool */ public static function setRole(string $role): void { - $userIdetifier = Role::parse($role)->getIdentifier(); - if ($userIdetifier) { + $parsedRole = Role::parse($role); + if ($parsedRole->getRole() === 'user') { + $userIdetifier = $parsedRole->getIdentifier(); self::$user = $userIdetifier; } self::$roles[$role] = true; @@ -102,8 +103,8 @@ public static function setRole(string $role): void */ public static function unsetRole(string $role): void { - $userIdetifier = Role::parse($role)->getIdentifier(); - if ($userIdetifier && self::$user == $userIdetifier) { + $parsedRole = Role::parse($role); + if ($parsedRole->getRole() === 'user' && self::$user === $parsedRole->getIdentifier()) { self::$user = null; } unset(self::$roles[$role]); diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 79627d409..664ecfa87 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -89,7 +89,7 @@ class Structure extends Validator '$id' => '$createdBy', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => Database::LENGTH_KEY, 'signed' => false, 'required' => false, 'default' => null, @@ -100,7 +100,7 @@ class Structure extends Validator '$id' => '$updatedBy', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => Database::LENGTH_KEY, 'signed' => false, 'required' => false, 'default' => null, diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 6481cc166..62462c2b7 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -6024,7 +6024,6 @@ public function testCreatedByUpdatedBy(): void $this->assertEquals('test_user_1', $retrievedDoc1->getCreatedBy()); $this->assertEquals('test_user_1', $retrievedDoc1->getUpdatedBy()); - $doc2 = $database->createDocument($collection, new Document([ '$id' => 'test2', 'string' => 'test2', @@ -6037,12 +6036,10 @@ public function testCreatedByUpdatedBy(): void $this->assertEquals('explicit_creator', $doc2->getCreatedBy()); $this->assertEquals('test_user_1', $doc2->getUpdatedBy()); - $retrievedDoc2 = $database->getDocument($collection, 'test2'); $this->assertEquals('explicit_creator', $retrievedDoc2->getCreatedBy()); $this->assertEquals('test_user_1', $retrievedDoc2->getUpdatedBy()); - $doc3 = $database->createDocument($collection, new Document([ '$id' => 'test3', 'string' => 'test3', @@ -6051,16 +6048,13 @@ public function testCreatedByUpdatedBy(): void '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] ])); - $this->assertEquals('test_user_1', $doc3->getCreatedBy()); $this->assertEquals('explicit_updater', $doc3->getUpdatedBy()); - $retrievedDoc3 = $database->getDocument($collection, 'test3'); $this->assertEquals('test_user_1', $retrievedDoc3->getCreatedBy()); $this->assertEquals('explicit_updater', $retrievedDoc3->getUpdatedBy()); - $doc4 = $database->createDocument($collection, new Document([ '$id' => 'test4', 'string' => 'test4', @@ -6070,20 +6064,16 @@ public function testCreatedByUpdatedBy(): void '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] ])); - $this->assertEquals('explicit_creator_2', $doc4->getCreatedBy()); $this->assertEquals('explicit_updater_2', $doc4->getUpdatedBy()); - $retrievedDoc4 = $database->getDocument($collection, 'test4'); $this->assertEquals('explicit_creator_2', $retrievedDoc4->getCreatedBy()); $this->assertEquals('explicit_updater_2', $retrievedDoc4->getUpdatedBy()); - $allDocs = $database->find($collection); $this->assertCount(4, $allDocs); - Authorization::setRole('user:test_user_2'); $updateDoc = new Document([ 'string' => 'updated_test1', @@ -6127,7 +6117,6 @@ public function testCreatedByUpdatedBy(): void $this->assertEquals('new_creator', $newUpsertedDoc->getCreatedBy()); // Should use explicit creator $this->assertEquals('test_user_2', $newUpsertedDoc->getUpdatedBy()); // Should use current user - $bulkDocs = [ new Document([ '$id' => 'bulk1', diff --git a/tests/unit/Validator/AuthorizationTest.php b/tests/unit/Validator/AuthorizationTest.php index 883861367..85ca1a233 100644 --- a/tests/unit/Validator/AuthorizationTest.php +++ b/tests/unit/Validator/AuthorizationTest.php @@ -130,6 +130,11 @@ public function testSetUserFromRoles(): void $roles[] = Role::user("123"); $roles[] = Role::user("123", 'verififed'); $roles[] = Role::user("126", 'unverififed'); + $roles[] = Role::member("member123"); + $roles[] = Role::team("team1", ); + $roles[] = Role::team("team2", 'verified'); + $roles[] = Role::label("label"); + $roles[] = Role::guests(); $roles[] = Role::any(); $roles[] = Role::users(); @@ -138,5 +143,14 @@ public function testSetUserFromRoles(): void } $expectedRole = Role::user("126", 'unverififed'); $this->assertEquals($expectedRole->getIdentifier(), Authorization::getUser()); + + // unsetting non set user + Authorization::unsetRole(Role::user("123")->toString()); + $expectedRole = Role::user("126", 'unverififed'); + $this->assertEquals($expectedRole->getIdentifier(), Authorization::getUser()); + + // unsetting set user + Authorization::unsetRole(Role::user("126")->toString()); + $this->assertEquals(null, Authorization::getUser()); } } From 5e17969662b1f0d9ac973cc9b871ce0968e0932e Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 15 Sep 2025 13:29:15 +0530 Subject: [PATCH 10/17] updated failing test due to collection name --- tests/e2e/Adapter/Scopes/DocumentTests.php | 63 ++++++++++++---------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 62462c2b7..395b64b96 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -373,41 +373,46 @@ public function testCreateDocuments(): void public function testCreateDocumentsWithAutoIncrement(): void { - /** @var Database $database */ - $database = static::getDatabase(); + $collectionName = 'tesstAutoIncr'; + try { + /** @var Database $database */ + $database = static::getDatabase(); - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); - $this->assertEquals(true, $database->createAttribute(__FUNCTION__, 'string', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, $database->createAttribute($collectionName, 'string', Database::VAR_STRING, 128, true)); - /** @var array $documents */ - $documents = []; - $count = 10; - $sequence = 1_000_000; + /** @var array $documents */ + $documents = []; + $count = 10; + $sequence = 1_000_000; - for ($i = $sequence; $i <= ($sequence + $count); $i++) { - $documents[] = new Document([ - '$sequence' => (string)$i, - '$permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'string' => 'text', - ]); - } + for ($i = $sequence; $i <= ($sequence + $count); $i++) { + $documents[] = new Document([ + '$sequence' => (string)$i, + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'string' => 'text', + ]); + } - $count = $database->createDocuments(__FUNCTION__, $documents, 6); - $this->assertEquals($count, \count($documents)); + $count = $database->createDocuments($collectionName, $documents, 6); + $this->assertEquals($count, \count($documents)); - $documents = $database->find(__FUNCTION__, [ - Query::orderAsc() - ]); - foreach ($documents as $index => $document) { - $this->assertEquals($sequence + $index, $document->getSequence()); - $this->assertNotEmpty(true, $document->getId()); - $this->assertEquals('text', $document->getAttribute('string')); + $documents = $database->find($collectionName, [ + Query::orderAsc() + ]); + foreach ($documents as $index => $document) { + $this->assertEquals($sequence + $index, $document->getSequence()); + $this->assertNotEmpty(true, $document->getId()); + $this->assertEquals('text', $document->getAttribute('string')); + } + } finally { + $database->deleteCollection($collectionName); } } From 1140b58013a4f7cfce5224e0ddd40854652dd015 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 15 Sep 2025 13:53:13 +0530 Subject: [PATCH 11/17] updated collection names --- tests/e2e/Adapter/Scopes/AttributeTests.php | 38 ++++++++++--------- tests/e2e/Adapter/Scopes/DocumentTests.php | 27 ++++++------- .../Scopes/Relationships/ManyToOneTests.php | 4 +- .../Scopes/Relationships/OneToManyTests.php | 4 +- .../Scopes/Relationships/OneToOneTests.php | 4 +- tests/e2e/Adapter/Scopes/SpatialTests.php | 16 ++++---- 6 files changed, 49 insertions(+), 44 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index de8b9c2bc..a9524e210 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -1816,13 +1816,13 @@ public function testCreateAttributesMissingRequired(): void { /** @var Database $database */ $database = static::getDatabase(); - + $collectionName = uniqid('test_attr_missing'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); $attributes = [[ '$id' => 'foo', @@ -1830,7 +1830,7 @@ public function testCreateAttributesMissingRequired(): void 'size' => 10 ]]; try { - $database->createAttributes(__FUNCTION__, $attributes); + $database->createAttributes($collectionName, $attributes); $this->fail('Expected DatabaseException not thrown'); } catch (\Throwable $e) { $this->assertInstanceOf(DatabaseException::class, $e); @@ -1841,14 +1841,15 @@ public function testCreateAttributesDuplicateMetadata(): void { /** @var Database $database */ $database = static::getDatabase(); + $collecitonName = uniqid('test_attr_dup'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); - $database->createAttribute(__FUNCTION__, 'dup', Database::VAR_STRING, 10, false); + $database->createCollection($collecitonName); + $database->createAttribute($collecitonName, 'dup', Database::VAR_STRING, 10, false); $attributes = [[ '$id' => 'dup', @@ -1858,7 +1859,7 @@ public function testCreateAttributesDuplicateMetadata(): void ]]; try { - $database->createAttributes(__FUNCTION__, $attributes); + $database->createAttributes($collecitonName, $attributes); $this->fail('Expected DuplicateException not thrown'); } catch (\Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); @@ -1924,13 +1925,14 @@ public function testCreateAttributesDefaultOnRequired(): void { /** @var Database $database */ $database = static::getDatabase(); + $collectionName = uniqid('test_attr_def_req'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); $attributes = [[ '$id' => 'foo', @@ -1941,7 +1943,7 @@ public function testCreateAttributesDefaultOnRequired(): void ]]; try { - $database->createAttributes(__FUNCTION__, $attributes); + $database->createAttributes($collectionName, $attributes); $this->fail('Expected DatabaseException not thrown'); } catch (\Throwable $e) { $this->assertInstanceOf(DatabaseException::class, $e); @@ -1979,13 +1981,13 @@ public function testCreateAttributesStringSizeLimit(): void { /** @var Database $database */ $database = static::getDatabase(); - + $collectionName = uniqid('test_str_size'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); $max = $database->getAdapter()->getLimitForString(); @@ -1997,7 +1999,7 @@ public function testCreateAttributesStringSizeLimit(): void ]]; try { - $database->createAttributes(__FUNCTION__, $attributes); + $database->createAttributes($collectionName, $attributes); $this->fail('Expected DatabaseException not thrown'); } catch (\Throwable $e) { $this->assertInstanceOf(DatabaseException::class, $e); @@ -2008,13 +2010,14 @@ public function testCreateAttributesIntegerSizeLimit(): void { /** @var Database $database */ $database = static::getDatabase(); + $collectionName = uniqid('test_int_size'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); $limit = $database->getAdapter()->getLimitForInt() / 2; @@ -2026,7 +2029,7 @@ public function testCreateAttributesIntegerSizeLimit(): void ]]; try { - $database->createAttributes(__FUNCTION__, $attributes); + $database->createAttributes($collectionName, $attributes); $this->fail('Expected DatabaseException not thrown'); } catch (\Throwable $e) { $this->assertInstanceOf(DatabaseException::class, $e); @@ -2037,13 +2040,14 @@ public function testCreateAttributesSuccessMultiple(): void { /** @var Database $database */ $database = static::getDatabase(); + $collectionName = uniqid('test_attr_success'); if (!$database->getAdapter()->getSupportForBatchCreateAttributes()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__); + $database->createCollection($collectionName); $attributes = [ [ @@ -2060,16 +2064,16 @@ public function testCreateAttributesSuccessMultiple(): void ], ]; - $result = $database->createAttributes(__FUNCTION__, $attributes); + $result = $database->createAttributes($collectionName, $attributes); $this->assertTrue($result); - $collection = $database->getCollection(__FUNCTION__); + $collection = $database->getCollection($collectionName); $attrs = $collection->getAttribute('attributes'); $this->assertCount(2, $attrs); $this->assertEquals('a', $attrs[0]['$id']); $this->assertEquals('b', $attrs[1]['$id']); - $doc = $database->createDocument(__FUNCTION__, new Document([ + $doc = $database->createDocument($collectionName, new Document([ 'a' => 'foo', 'b' => 123, ])); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 395b64b96..b7278edf7 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -374,9 +374,9 @@ public function testCreateDocuments(): void public function testCreateDocumentsWithAutoIncrement(): void { $collectionName = 'tesstAutoIncr'; + /** @var Database $database */ + $database = static::getDatabase(); try { - /** @var Database $database */ - $database = static::getDatabase(); $database->createCollection($collectionName); @@ -840,22 +840,23 @@ public function testUpsertDocumentsAttributeMismatch(): void { /** @var Database $database */ $database = static::getDatabase(); + $collectionName = uniqid("test_attr_"); if (!$database->getAdapter()->getSupportForUpserts()) { $this->expectNotToPerformAssertions(); return; } - $database->createCollection(__FUNCTION__, permissions: [ + $database->createCollection($collectionName, permissions: [ Permission::create(Role::any()), Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any()), ], documentSecurity: false); - $database->createAttribute(__FUNCTION__, 'first', Database::VAR_STRING, 128, true); - $database->createAttribute(__FUNCTION__, 'last', Database::VAR_STRING, 128, false); + $database->createAttribute($collectionName, 'first', Database::VAR_STRING, 128, true); + $database->createAttribute($collectionName, 'last', Database::VAR_STRING, 128, false); - $existingDocument = $database->createDocument(__FUNCTION__, new Document([ + $existingDocument = $database->createDocument($collectionName, new Document([ '$id' => 'first', 'first' => 'first', 'last' => 'last', @@ -867,7 +868,7 @@ public function testUpsertDocumentsAttributeMismatch(): void ]); // Ensure missing optionals on new document is allowed - $docs = $database->createOrUpdateDocuments(__FUNCTION__, [ + $docs = $database->createOrUpdateDocuments($collectionName, [ $existingDocument->setAttribute('first', 'updated'), $newDocument, ]); @@ -879,7 +880,7 @@ public function testUpsertDocumentsAttributeMismatch(): void $this->assertEquals('', $newDocument->getAttribute('last')); try { - $database->createOrUpdateDocuments(__FUNCTION__, [ + $database->createOrUpdateDocuments($collectionName, [ $existingDocument->removeAttribute('first'), $newDocument ]); @@ -889,7 +890,7 @@ public function testUpsertDocumentsAttributeMismatch(): void } // Ensure missing optionals on existing document is allowed - $docs = $database->createOrUpdateDocuments(__FUNCTION__, [ + $docs = $database->createOrUpdateDocuments($collectionName, [ $existingDocument ->setAttribute('first', 'first') ->removeAttribute('last'), @@ -904,7 +905,7 @@ public function testUpsertDocumentsAttributeMismatch(): void $this->assertEquals('last', $newDocument->getAttribute('last')); // Ensure set null on existing document is allowed - $docs = $database->createOrUpdateDocuments(__FUNCTION__, [ + $docs = $database->createOrUpdateDocuments($collectionName, [ $existingDocument ->setAttribute('first', 'first') ->setAttribute('last', null), @@ -931,7 +932,7 @@ public function testUpsertDocumentsAttributeMismatch(): void ]); // Ensure mismatch of attribute orders is allowed - $docs = $database->createOrUpdateDocuments(__FUNCTION__, [ + $docs = $database->createOrUpdateDocuments($collectionName, [ $doc3, $doc4 ]); @@ -942,8 +943,8 @@ public function testUpsertDocumentsAttributeMismatch(): void $this->assertEquals('fourth', $doc4->getAttribute('first')); $this->assertEquals('last', $doc4->getAttribute('last')); - $doc3 = $database->getDocument(__FUNCTION__, 'third'); - $doc4 = $database->getDocument(__FUNCTION__, 'fourth'); + $doc3 = $database->getDocument($collectionName, 'third'); + $doc4 = $database->getDocument($collectionName, 'fourth'); $this->assertEquals('third', $doc3->getAttribute('first')); $this->assertEquals('last', $doc3->getAttribute('last')); diff --git a/tests/e2e/Adapter/Scopes/Relationships/ManyToOneTests.php b/tests/e2e/Adapter/Scopes/Relationships/ManyToOneTests.php index 73a1c7a6f..05e3adce9 100644 --- a/tests/e2e/Adapter/Scopes/Relationships/ManyToOneTests.php +++ b/tests/e2e/Adapter/Scopes/Relationships/ManyToOneTests.php @@ -1770,8 +1770,8 @@ public function testDeleteDocumentsRelationshipErrorDoesNotDeleteParent_ManyToOn return; } - $parentCollection = 'parent_relationship_error_many_to_one'; - $childCollection = 'child_relationship_error_many_to_one'; + $parentCollection = uniqid('parent_rel'); + $childCollection = uniqid('child_rel'); $database->createCollection($parentCollection); $database->createCollection($childCollection); diff --git a/tests/e2e/Adapter/Scopes/Relationships/OneToManyTests.php b/tests/e2e/Adapter/Scopes/Relationships/OneToManyTests.php index b29bf2087..681dbfc6d 100644 --- a/tests/e2e/Adapter/Scopes/Relationships/OneToManyTests.php +++ b/tests/e2e/Adapter/Scopes/Relationships/OneToManyTests.php @@ -2165,8 +2165,8 @@ public function testDeleteDocumentsRelationshipErrorDoesNotDeleteParent_OneToMan return; } - $parentCollection = 'parent_relationship_error_one_to_many'; - $childCollection = 'child_relationship_error_one_to_many'; + $parentCollection = uniqid('parent_rel'); + $childCollection = uniqid('child_rel'); $database->createCollection($parentCollection); $database->createCollection($childCollection); diff --git a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php index 52881707b..ed950b0cd 100644 --- a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php +++ b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php @@ -2382,8 +2382,8 @@ public function testDeleteDocumentsRelationshipErrorDoesNotDeleteParent_OneToOne return; } - $parentCollection = 'parent_relationship_error_one_to_one'; - $childCollection = 'child_relationship_error_one_to_one'; + $parentCollection = uniqid('parent_rel'); + $childCollection = uniqid('child_rel'); $database->createCollection($parentCollection); $database->createCollection($childCollection); diff --git a/tests/e2e/Adapter/Scopes/SpatialTests.php b/tests/e2e/Adapter/Scopes/SpatialTests.php index 4874f2ee6..f01b1c1f1 100644 --- a/tests/e2e/Adapter/Scopes/SpatialTests.php +++ b/tests/e2e/Adapter/Scopes/SpatialTests.php @@ -722,7 +722,7 @@ public function testSpatialIndex(): void } // Basic spatial index create/delete - $collectionName = 'spatial_index_'; + $collectionName = uniqid('sp_idx'); try { $database->createCollection($collectionName); $database->createAttribute($collectionName, 'loc', Database::VAR_POINT, 0, true); @@ -745,7 +745,7 @@ public function testSpatialIndex(): void $orderSupported = $database->getAdapter()->getSupportForSpatialIndexOrder(); // createCollection with orders - $collOrderCreate = 'spatial_idx_order_create'; + $collOrderCreate = uniqid('sp_idx_'); try { $attributes = [new Document([ '$id' => ID::custom('loc'), @@ -783,7 +783,7 @@ public function testSpatialIndex(): void } // createIndex with orders - $collOrderIndex = 'spatial_idx_order_index_' . uniqid(); + $collOrderIndex = uniqid('sp_idx_'); try { $database->createCollection($collOrderIndex); $database->createAttribute($collOrderIndex, 'loc', Database::VAR_POINT, 0, true); @@ -805,7 +805,7 @@ public function testSpatialIndex(): void $nullSupported = $database->getAdapter()->getSupportForSpatialIndexNull(); // createCollection with required=false - $collNullCreate = 'spatial_idx_null_create_' . uniqid(); + $collNullCreate = uniqid('sp_idx'); try { $attributes = [new Document([ '$id' => ID::custom('loc'), @@ -843,7 +843,7 @@ public function testSpatialIndex(): void } // createIndex with required=false - $collNullIndex = 'spatial_idx_null_index_' . uniqid(); + $collNullIndex = uniqid('sp_idx_'); try { $database->createCollection($collNullIndex); $database->createAttribute($collNullIndex, 'loc', Database::VAR_POINT, 0, false); @@ -861,7 +861,7 @@ public function testSpatialIndex(): void $database->deleteCollection($collNullIndex); } - $collUpdateNull = 'spatial_idx_req'; + $collUpdateNull = uniqid('sp_idx'); try { $database->createCollection($collUpdateNull); @@ -885,7 +885,7 @@ public function testSpatialIndex(): void } - $collUpdateNull = 'spatial_idx_index_null_required_true'; + $collUpdateNull = uniqid('sp_idx'); try { $database->createCollection($collUpdateNull); @@ -2486,7 +2486,7 @@ public function testSpatialIndexSingleAttributeOnly(): void return; } - $collectionName = 'spatial_idx_single_attr_' . uniqid(); + $collectionName = uniqid("sp_idx"); try { $database->createCollection($collectionName); From c20f225a31dee3bd4862cc5009a0a95194f0e901 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 15 Sep 2025 13:55:03 +0530 Subject: [PATCH 12/17] linting --- tests/e2e/Adapter/Scopes/DocumentTests.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index b7278edf7..ce9733f82 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -6007,12 +6007,10 @@ public function testCreatedByUpdatedBy(): void $collection = 'test_created_updated_by'; $database = static::getDatabase(); - $database->createCollection($collection); $database->createAttribute($collection, 'string', Database::VAR_STRING, 128, false); $database->createAttribute($collection, 'number', Database::VAR_INTEGER, 0, false); - Authorization::setRole('user:test_user_1'); $doc1 = $database->createDocument($collection, new Document([ '$id' => 'test1', @@ -6021,11 +6019,9 @@ public function testCreatedByUpdatedBy(): void '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] ])); - $this->assertEquals('test_user_1', $doc1->getCreatedBy()); $this->assertEquals('test_user_1', $doc1->getUpdatedBy()); - $retrievedDoc1 = $database->getDocument($collection, 'test1'); $this->assertEquals('test_user_1', $retrievedDoc1->getCreatedBy()); $this->assertEquals('test_user_1', $retrievedDoc1->getUpdatedBy()); @@ -6038,7 +6034,6 @@ public function testCreatedByUpdatedBy(): void '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] ])); - $this->assertEquals('explicit_creator', $doc2->getCreatedBy()); $this->assertEquals('test_user_1', $doc2->getUpdatedBy()); From e7063903683e331cc06c18d9a704852c42ac4fde Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 16 Sep 2025 01:27:35 +0530 Subject: [PATCH 13/17] updated updatedby in updatedocuments --- src/Database/Database.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index fc193bb4b..50c94ac09 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4410,7 +4410,8 @@ public function updateDocument(string $collection, string $id, Document $documen $collection = $this->silent(fn () => $this->getCollection($collection)); $newUpdatedAt = $document->getUpdatedAt(); - $document = $this->withTransaction(function () use ($collection, $id, $document, $newUpdatedAt) { + $newUpdatedBy = $document->getUpdatedBy(); + $document = $this->withTransaction(function () use ($collection, $id, $document, $newUpdatedAt, $newUpdatedBy) { $time = DateTime::now(); $old = Authorization::skip(fn () => $this->silent( fn () => $this->getDocument($collection->getId(), $id, forUpdate: true) @@ -4561,7 +4562,7 @@ public function updateDocument(string $collection, string $id, Document $documen if ($shouldUpdate) { $document->setAttribute('$updatedAt', ($newUpdatedAt === null || !$this->preserveDates) ? $time : $newUpdatedAt); - $document->setAttribute('$updatedBy', Authorization::getUser()); + $document->setAttribute('$updatedBy', $newUpdatedBy === null ? Authorization::getUser() : $newUpdatedBy); } // Check if document was updated after the request timestamp From 2cd46665333fb041192692539f50a5eb42de576e Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 16 Sep 2025 23:08:28 +0530 Subject: [PATCH 14/17] fixed updatedBy for increment and decrement attribute --- src/Database/Database.php | 10 ++-- tests/e2e/Adapter/Scopes/DocumentTests.php | 54 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 50c94ac09..b7360323a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5564,9 +5564,8 @@ public function increaseDocumentAttribute( max: $max ); - return $document->setAttribute( - $attribute, - $document->getAttribute($attribute) + $value + return $document->setAttributes( + [$attribute => $document->getAttribute($attribute) + $value, '$updatedAt' => $updatedAt, '$updatedBy' => $updatedBy] ); }); @@ -5665,9 +5664,8 @@ public function decreaseDocumentAttribute( min: $min ); - return $document->setAttribute( - $attribute, - $document->getAttribute($attribute) - $value + return $document->setAttributes( + [$attribute => $document->getAttribute($attribute) - $value, '$updatedAt' => $updatedAt, '$updatedBy' => $updatedBy] ); }); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index ce9733f82..1848f028c 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -6160,6 +6160,60 @@ public function testCreatedByUpdatedBy(): void $this->assertCount(8, $finalAllDocs); $database->deleteCollection($collection); + + // testing with increment decrement + Authorization::setRole('user:test_user_3'); + $collection = 'increase_decrease'; + $database->createCollection($collection); + + $this->assertEquals(true, $database->createAttribute($collection, 'increase', Database::VAR_INTEGER, 0, true)); + + $document = $database->createDocument($collection, new Document([ + 'increase' => 100, + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ] + ])); + + $doc = $database->increaseDocumentAttribute($collection, $document->getId(), 'increase', 1, 301); + $this->assertEquals(101, $doc->getAttribute('increase')); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_3', $doc->getUpdatedBy()); + + $document = $database->getDocument($collection, $document->getId()); + $this->assertEquals(101, $document->getAttribute('increase')); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_3', $doc->getUpdatedBy()); + + Authorization::setRole('user:test_user_4'); + $doc = $database->increaseDocumentAttribute($collection, $document->getId(), 'increase', 1, 301); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_4', $doc->getUpdatedBy()); + + $doc = $database->getDocument($collection, $document->getId()); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_4', $doc->getUpdatedBy()); + + $doc = $database->decreaseDocumentAttribute($collection, $document->getId(), 'increase', 1, -100); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_4', $doc->getUpdatedBy()); + + $doc = $database->getDocument($collection, $document->getId()); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_4', $doc->getUpdatedBy()); + + Authorization::setRole('user:test_user_5'); + + $doc = $database->decreaseDocumentAttribute($collection, $document->getId(), 'increase', 1, -100); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_5', $doc->getUpdatedBy()); + + $doc = $database->getDocument($collection, $document->getId()); + $this->assertEquals('test_user_3', $doc->getCreatedBy()); + $this->assertEquals('test_user_5', $doc->getUpdatedBy()); } } } From f4a449b2cc3567d3d902309e23b877e37544249b Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 16 Sep 2025 23:33:33 +0530 Subject: [PATCH 15/17] updated sql with the internal keys and sqlite with the new internal attributes --- src/Database/Adapter/SQL.php | 2 ++ src/Database/Adapter/SQLite.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index d72080d98..9c17fbe63 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1910,6 +1910,8 @@ protected function getAttributeProjection(array $selections, string $prefix): st '$permissions', '$createdAt', '$updatedAt', + '$createdBy', + '$updatedBy' ]; $selections = \array_diff($selections, [...$internalKeys, '$collection']); diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index d3ff4315c..51b0c3cae 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1038,6 +1038,8 @@ protected function getSQLIndex(string $collection, string $id, string $type, arr '$id' => ID::custom('_uid'), '$createdAt' => '_createdAt', '$updatedAt' => '_updatedAt', + '$createdBy' => '_createdBy', + '$updatedBy' => '_updatedBy', default => $attribute }, $attributes); From e95802fb07225a1616e5a242c379e8910e2f724a Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 16 Sep 2025 23:38:23 +0530 Subject: [PATCH 16/17] code quality checks --- tests/e2e/Adapter/Scopes/DocumentTests.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 6f68508fb..5ba88e030 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -893,7 +893,7 @@ public function testUpsertDocumentsAttributeMismatch(): void ]); // Ensure missing optionals on new document is allowed - $docs = $database->createOrUpdateDocuments($collectionName, [ + $docs = $database->upsertDocuments($collectionName, [ $existingDocument->setAttribute('first', 'updated'), $newDocument, ]); @@ -905,7 +905,7 @@ public function testUpsertDocumentsAttributeMismatch(): void $this->assertEquals('', $newDocument->getAttribute('last')); try { - $database->createOrUpdateDocuments($collectionName, [ + $database->upsertDocuments($collectionName, [ $existingDocument->removeAttribute('first'), $newDocument ]); @@ -915,7 +915,7 @@ public function testUpsertDocumentsAttributeMismatch(): void } // Ensure missing optionals on existing document is allowed - $docs = $database->createOrUpdateDocuments($collectionName, [ + $docs = $database->upsertDocuments($collectionName, [ $existingDocument ->setAttribute('first', 'first') ->removeAttribute('last'), @@ -930,7 +930,7 @@ public function testUpsertDocumentsAttributeMismatch(): void $this->assertEquals('last', $newDocument->getAttribute('last')); // Ensure set null on existing document is allowed - $docs = $database->createOrUpdateDocuments($collectionName, [ + $docs = $database->upsertDocuments($collectionName, [ $existingDocument ->setAttribute('first', 'first') ->setAttribute('last', null), @@ -957,7 +957,7 @@ public function testUpsertDocumentsAttributeMismatch(): void ]); // Ensure mismatch of attribute orders is allowed - $docs = $database->createOrUpdateDocuments($collectionName, [ + $docs = $database->upsertDocuments($collectionName, [ $doc3, $doc4 ]); @@ -6121,7 +6121,7 @@ public function testCreatedByUpdatedBy(): void 'number' => 250 ]); if ($database->getAdapter()->getSupportForUpserts()) { - $upsertCount = $database->createOrUpdateDocuments($collection, [$upsertDoc]); + $upsertCount = $database->upsertDocuments($collection, [$upsertDoc]); $this->assertEquals(1, $upsertCount); $upsertedDoc = $database->getDocument($collection, 'test2'); @@ -6136,7 +6136,7 @@ public function testCreatedByUpdatedBy(): void '$permissions' => [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any())] ]); - $upsertNewCount = $database->createOrUpdateDocuments($collection, [$upsertNewDoc]); + $upsertNewCount = $database->upsertDocuments($collection, [$upsertNewDoc]); $this->assertEquals(1, $upsertNewCount); $newUpsertedDoc = $database->getDocument($collection, 'test5'); From bd8a8ac426e3bfc81ebc22f7f6e09ac419d253c6 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 17 Sep 2025 01:08:05 +0530 Subject: [PATCH 17/17] updated postgres adapter and tests --- src/Database/Adapter/Postgres.php | 1 - tests/e2e/Adapter/Scopes/DocumentTests.php | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 41d5ac08b..3e813718e 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -239,7 +239,6 @@ public function createCollection(string $name, array $attributes = [], array $in \"_updatedAt\" TIMESTAMP(3) DEFAULT NULL, \"_createdBy\" VARCHAR(255) DEFAULT NULL, \"_updatedBy\" VARCHAR(255) DEFAULT NULL, - _permissions TEXT DEFAULT NULL, " . \implode(' ', $attributeStrings) . " _permissions TEXT DEFAULT NULL ); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 5ba88e030..adc06241e 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -1214,6 +1214,7 @@ public function testIncreaseDecrease(): Document $document = $database->getDocument($collection, $document->getId()); $this->assertEquals(104.4, $document->getAttribute('increase_float')); + $database->deleteCollection($collection); return $document; } @@ -6239,6 +6240,8 @@ public function testCreatedByUpdatedBy(): void $doc = $database->getDocument($collection, $document->getId()); $this->assertEquals('test_user_3', $doc->getCreatedBy()); $this->assertEquals('test_user_5', $doc->getUpdatedBy()); + + $database->deleteCollection($collection); } } }