From f0c10acbb445a0eede176c7b659b3784c9f67028 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 29 Aug 2025 19:30:59 +1200 Subject: [PATCH 1/5] Fix readonly attr stripping on write --- .../Collections/Documents/Action.php | 9 +-- .../Collections/Documents/Bulk/Update.php | 3 +- .../Collections/Documents/Bulk/Upsert.php | 1 + .../Collections/Documents/Create.php | 9 ++- .../Collections/Documents/Update.php | 11 +-- .../Collections/Documents/Upsert.php | 5 +- .../Databases/Legacy/DatabasesBase.php | 69 +++++++++++++++++++ 7 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index 78df15b0c1..e077545b11 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -27,9 +27,8 @@ abstract class Action extends AppwriteAction $this->context = ROWS; } - // Use the same helper method to ensure consistency $contextId = '$' . $this->getCollectionsEventsContext() . 'Id'; - $this->removableAttributes = ['$databaseId', $contextId, '$sequence']; + $this->removableAttributes = ['$sequence', '$databaseId', $contextId]; return parent::setHttpPath($path); } @@ -200,11 +199,13 @@ abstract class Action extends AppwriteAction * Remove configured removable attributes from a document. * Used for relationship path handling to remove API-specific attributes. */ - protected function removeReadonlyAttributes(Document $document): void + protected function removeReadonlyAttributes(Document|array $document): Document|array { foreach ($this->removableAttributes as $attribute) { - $document->removeAttribute($attribute); + \var_dump('Removing attribute: ' . $attribute); + unset($document[$attribute]); } + return $document; } /** diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index a9f9c3f76d..226303c657 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -127,8 +127,7 @@ class Update extends Action } } - // Remove sequence if set - unset($document['$sequence']); + $data = $this->removeReadonlyAttributes($data); $documents = []; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index a6f27637e3..6b9a81cd69 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -100,6 +100,7 @@ class Upsert extends Action } foreach ($documents as $key => $document) { + $document = $this->removeReadonlyAttributes($document); $documents[$key] = new Document($document); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 04c90c4ec1..4bd0c6cdee 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -298,6 +298,8 @@ class Create extends Action ); foreach ($relations as &$relation) { + $relation = $this->removeReadonlyAttributes($relation); + if ( \is_array($relation) && \array_values($relation) !== $relation @@ -318,7 +320,6 @@ class Create extends Action $relation['$id'] = ID::unique(); } } else { - $this->removeReadonlyAttributes($relation); $relation->setAttribute('$collection', $relatedCollection->getId()); $type = Database::PERMISSION_UPDATE; } @@ -351,9 +352,6 @@ class Create extends Action } } - // Remove sequence if set - unset($document['$sequence']); - // Assign a unique ID if needed, otherwise use the provided ID. $document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId; @@ -368,10 +366,11 @@ class Create extends Action } } + $document = $this->removeReadonlyAttributes($document); + $document = new Document($document); $setPermissions($document, $permissions); $checkPermissions($collection, $document, Database::PERMISSION_CREATE); - return $document; }, $documents); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index 334bcb8448..fd86fa995c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -159,17 +159,14 @@ class Update extends Action $permissions = $document->getPermissions() ?? []; } - // Remove sequence if set - unset($document['$sequence']); - $data['$id'] = $documentId; $data['$permissions'] = $permissions; + $data = $this->removeReadonlyAttributes($data); $newDocument = new Document($data); $operations = 0; $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { - $operations++; $relationships = \array_filter( @@ -198,6 +195,8 @@ class Update extends Action ); foreach ($relations as &$relation) { + $relation = $this->removeReadonlyAttributes($relation); + // If the relation is an array it can be either update or create a child document. if ( \is_array($relation) @@ -212,7 +211,7 @@ class Update extends Action 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() )); - $this->removeReadonlyAttributes($relation); + // Attribute $collection is required for Utopia. $relation->setAttribute( '$collection', @@ -242,6 +241,8 @@ class Update extends Action ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations); + \var_dump($newDocument); + try { $document = $dbForProject->withRequestTimestamp( $requestTimestamp, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 6027a20c41..622b43db2a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -170,6 +170,7 @@ class Upsert extends Action $data['$id'] = $documentId; $data['$permissions'] = $permissions ?? []; + $data = $this->removeReadonlyAttributes($data); $newDocument = new Document($data); $operations = 0; @@ -203,6 +204,8 @@ class Upsert extends Action ); foreach ($relations as &$relation) { + $relation = $this->removeReadonlyAttributes($relation); + // If the relation is an array it can be either update or create a child document. if ( \is_array($relation) @@ -217,7 +220,7 @@ class Upsert extends Action 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() )); - $this->removeReadonlyAttributes($relation); + // Attribute $collection is required for Utopia. $relation->setAttribute( '$collection', diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index bd71272537..0df0a2f5af 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1697,6 +1697,7 @@ trait DatabasesBase return $data; } + /** * @depends testCreateIndexes */ @@ -2211,6 +2212,49 @@ trait DatabasesBase $this->assertArrayHasKey('$permissions', $library3['body']); $this->assertCount(3, $library3['body']['$permissions']); $this->assertNotEmpty($library3['body']['$permissions']); + + // Readonly attributes are ignored + $personNoPerm = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + '$id' => 'some-other-id', + '$collectionId' => 'some-other-collection', + '$databaseId' => 'some-other-database', + '$createdAt' => '2024-01-01T00:00:00Z', + '$updatedAt' => '2024-01-01T00:00:00Z', + 'library' => [ + '$id' => 'library3', + 'libraryName' => 'Library 3', + '$createdAt' => '2024-01-01T00:00:00Z', + '$updatedAt' => '2024-01-01T00:00:00Z', + ], + ], + ]); + + $update = $personNoPerm; + $update['body']['$id'] = 'random'; + $update['body']['$sequence'] = 123; + $update['body']['$databaseId'] = 'random'; + $update['body']['$collectionId'] = 'random'; + $update['body']['$createdAt'] = '2024-01-01T00:00:00Z'; + $update['body']['$updatedAt'] = '2024-01-01T00:00:00Z'; + + $upserted = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => $update['body'] + ]); + + $this->assertEquals(200, $upserted['headers']['status-code']); + $this->assertEquals($personNoPerm['body']['$id'], $upserted['body']['$id']); + $this->assertEquals($personNoPerm['body']['$collectionId'], $upserted['body']['$collectionId']); + $this->assertEquals($personNoPerm['body']['$databaseId'], $upserted['body']['$databaseId']); + $this->assertEquals($personNoPerm['body']['$sequence'], $upserted['body']['$sequence']); + $this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']); } } @@ -3000,6 +3044,31 @@ trait DatabasesBase $this->assertEquals(200, $response['headers']['status-code']); + // Test readonly attributes are ignored + $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-timestamp' => DateTime::formatTz(DateTime::now()), + ], $this->getHeaders()), [ + 'data' => [ + '$id' => 'newId', + '$sequence' => 9999, + '$collectionId' => 'newCollectionId', + '$databaseId' => 'newDatabaseId', + '$createdAt' => '2024-01-01T00:00:00+00:00', + '$updatedAt' => '2024-01-01T00:00:00+00:00', + 'title' => 'Thor: Ragnarok', + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($id, $response['body']['$id']); + $this->assertEquals($data['moviesId'], $response['body']['$collectionId']); + $this->assertEquals($databaseId, $response['body']['$databaseId']); + $this->assertNotEquals('2024-01-01T00:00:00+00:00', $response['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00+00:00', $response['body']['$updatedAt']); + $this->assertNotEquals(9999, $response['body']['$sequence']); + return []; } From d9aadb02e7a316c2743a24e0a4283a02859fe7fd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 29 Aug 2025 19:53:47 +1200 Subject: [PATCH 2/5] Move filter --- .../Databases/Http/Databases/Collections/Documents/Create.php | 4 ++-- .../Databases/Http/Databases/Collections/Documents/Delete.php | 1 + .../Databases/Http/Databases/Collections/Documents/Update.php | 4 ++-- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 4bd0c6cdee..6222c6c183 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -298,8 +298,6 @@ class Create extends Action ); foreach ($relations as &$relation) { - $relation = $this->removeReadonlyAttributes($relation); - if ( \is_array($relation) && \array_values($relation) !== $relation @@ -309,6 +307,8 @@ class Create extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation); + $current = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId()) ); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 4ae1624f73..f34b4630c2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -117,6 +117,7 @@ class Delete extends Action } $collectionsCache = []; + $this->processDocument( database: $database, collection: $collection, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index fd86fa995c..7512b1aab0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -195,8 +195,6 @@ class Update extends Action ); foreach ($relations as &$relation) { - $relation = $this->removeReadonlyAttributes($relation); - // If the relation is an array it can be either update or create a child document. if ( \is_array($relation) @@ -207,6 +205,8 @@ class Update extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation); + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 622b43db2a..004777c1b2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -204,8 +204,6 @@ class Upsert extends Action ); foreach ($relations as &$relation) { - $relation = $this->removeReadonlyAttributes($relation); - // If the relation is an array it can be either update or create a child document. if ( \is_array($relation) @@ -216,6 +214,8 @@ class Upsert extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation); + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() From 57071af3e87857eb86dcb0376276e3b6e62a39bf Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 29 Aug 2025 20:38:55 +1200 Subject: [PATCH 3/5] Handle privileged --- .../Collections/Documents/Action.php | 25 ++++++++++++++--- .../Collections/Documents/Bulk/Update.php | 2 +- .../Collections/Documents/Bulk/Upsert.php | 2 +- .../Collections/Documents/Create.php | 19 ++----------- .../Collections/Documents/Update.php | 16 ++--------- .../Collections/Documents/Upsert.php | 20 ++++--------- .../Databases/Collections/Documents/XList.php | 4 +-- src/Appwrite/Platform/Workers/Migrations.php | 1 - .../Databases/Legacy/DatabasesBase.php | 28 +++++++++++++------ 9 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index e077545b11..c952b3bc6e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -28,7 +28,17 @@ abstract class Action extends AppwriteAction } $contextId = '$' . $this->getCollectionsEventsContext() . 'Id'; - $this->removableAttributes = ['$sequence', '$databaseId', $contextId]; + $this->removableAttributes = [ + '*' => [ + '$sequence', + '$databaseId', + $contextId, + ], + 'privileged' => [ + '$createdAt', + '$updatedAt', + ], + ]; return parent::setHttpPath($path); } @@ -199,12 +209,19 @@ abstract class Action extends AppwriteAction * Remove configured removable attributes from a document. * Used for relationship path handling to remove API-specific attributes. */ - protected function removeReadonlyAttributes(Document|array $document): Document|array + protected function removeReadonlyAttributes( + Document|array $document, + bool $privileged = false, + ): Document|array { - foreach ($this->removableAttributes as $attribute) { - \var_dump('Removing attribute: ' . $attribute); + foreach ($this->removableAttributes['*'] as $attribute) { unset($document[$attribute]); } + if (!$privileged) { + foreach ($this->removableAttributes['privileged'] ?? [] as $attribute) { + unset($document[$attribute]); + } + } return $document; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 226303c657..0f0ae14020 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -127,7 +127,7 @@ class Update extends Action } } - $data = $this->removeReadonlyAttributes($data); + $data = $this->removeReadonlyAttributes($data, privileged: true); $documents = []; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 6b9a81cd69..3c6e5ddc57 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -100,7 +100,7 @@ class Upsert extends Action } foreach ($documents as $key => $document) { - $document = $this->removeReadonlyAttributes($document); + $document = $this->removeReadonlyAttributes($document, privileged: true); $documents[$key] = new Document($document); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 6222c6c183..d274e1f128 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -254,7 +254,7 @@ class Create extends Action $operations = 0; - $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, &$operations) { + $checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations) { $operations++; $documentSecurity = $collection->getAttribute('documentSecurity', false); @@ -307,7 +307,7 @@ class Create extends Action $relation = new Document($relation); } if ($relation instanceof Document) { - $relation = $this->removeReadonlyAttributes($relation); + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); $current = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId()) @@ -354,20 +354,7 @@ class Create extends Action // Assign a unique ID if needed, otherwise use the provided ID. $document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId; - - // Allowing to add createdAt and updatedAt timestamps if server side(api key - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($document['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($document['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } - - $document = $this->removeReadonlyAttributes($document); - + $document = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser); $document = new Document($document); $setPermissions($document, $permissions); $checkPermissions($collection, $document, Database::PERMISSION_CREATE); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index 7512b1aab0..556d14219f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -109,16 +109,6 @@ class Update extends Action throw new Exception($this->getParentNotFoundException()); } - // Allowing to add createdAt and updatedAt timestamps if server side(api key) - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($data['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($data['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } // Read permission should not be required for update /** @var Document $document */ $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); @@ -161,12 +151,12 @@ class Update extends Action $data['$id'] = $documentId; $data['$permissions'] = $permissions; - $data = $this->removeReadonlyAttributes($data); + $data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser); $newDocument = new Document($data); $operations = 0; - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { + $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) { $operations++; $relationships = \array_filter( @@ -205,7 +195,7 @@ class Update extends Action $relation = new Document($relation); } if ($relation instanceof Document) { - $relation = $this->removeReadonlyAttributes($relation); + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 004777c1b2..54b1cad950 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -121,7 +121,8 @@ class Upsert extends Action ]; $permissions = Permission::aggregate($permissions, $allowedPermissions); - // if no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) + + // If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) if (\is_null($permissions)) { $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); if ($oldDocument->isEmpty()) { @@ -157,25 +158,14 @@ class Upsert extends Action } } } - // Allowing to add createdAt and updatedAt timestamps if server side(api key) - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($data['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($data['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } $data['$id'] = $documentId; $data['$permissions'] = $permissions ?? []; - $data = $this->removeReadonlyAttributes($data); + $data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser); $newDocument = new Document($data); $operations = 0; - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { - + $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) { $operations++; $relationships = \array_filter( @@ -214,7 +204,7 @@ class Upsert extends Action $relation = new Document($relation); } if ($relation instanceof Document) { - $relation = $this->removeReadonlyAttributes($relation); + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 57ca550c18..9c8405cf18 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -176,7 +176,7 @@ class XList extends Action } // Check which removable attributes are explicitly requested - foreach ($this->removableAttributes as $attribute) { + foreach ($this->removableAttributes['*'] as $attribute) { if (\in_array($attribute, $values, true)) { $requestedAttributes[$attribute] = true; } @@ -186,7 +186,7 @@ class XList extends Action if (!$hasWildcard) { foreach ($documents as $document) { // Remove attributes that are not explicitly requested - foreach ($this->removableAttributes as $attribute) { + foreach ($this->removableAttributes['*'] as $attribute) { if (!isset($requestedAttributes[$attribute])) { $document->removeAttribute($attribute); } diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index ff25e799c1..a36c563747 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -212,7 +212,6 @@ class Migrations extends Action // set the errors back without trace $clonedMigrationDocument->setAttribute('errors', $errorMessages); - /** Trigger Realtime Events */ $queueForRealtime ->setProject($project) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 0df0a2f5af..fbd0714d26 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -2238,8 +2238,8 @@ trait DatabasesBase $update['body']['$sequence'] = 123; $update['body']['$databaseId'] = 'random'; $update['body']['$collectionId'] = 'random'; - $update['body']['$createdAt'] = '2024-01-01T00:00:00Z'; - $update['body']['$updatedAt'] = '2024-01-01T00:00:00Z'; + $update['body']['$createdAt'] = '2024-01-01T00:00:00.000+00:00'; + $update['body']['$updatedAt'] = '2024-01-01T00:00:00.000+00:00'; $upserted = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([ 'content-type' => 'application/json', @@ -2253,8 +2253,14 @@ trait DatabasesBase $this->assertEquals($personNoPerm['body']['$collectionId'], $upserted['body']['$collectionId']); $this->assertEquals($personNoPerm['body']['$databaseId'], $upserted['body']['$databaseId']); $this->assertEquals($personNoPerm['body']['$sequence'], $upserted['body']['$sequence']); - $this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']); - $this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']); + + if ($this->getSide() === 'client') { + $this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']); + } else { + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$createdAt']); + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']); + } } } @@ -3055,8 +3061,8 @@ trait DatabasesBase '$sequence' => 9999, '$collectionId' => 'newCollectionId', '$databaseId' => 'newDatabaseId', - '$createdAt' => '2024-01-01T00:00:00+00:00', - '$updatedAt' => '2024-01-01T00:00:00+00:00', + '$createdAt' => '2024-01-01T00:00:00.000+00:00', + '$updatedAt' => '2024-01-01T00:00:00.000+00:00', 'title' => 'Thor: Ragnarok', ], ]); @@ -3065,10 +3071,16 @@ trait DatabasesBase $this->assertEquals($id, $response['body']['$id']); $this->assertEquals($data['moviesId'], $response['body']['$collectionId']); $this->assertEquals($databaseId, $response['body']['$databaseId']); - $this->assertNotEquals('2024-01-01T00:00:00+00:00', $response['body']['$createdAt']); - $this->assertNotEquals('2024-01-01T00:00:00+00:00', $response['body']['$updatedAt']); $this->assertNotEquals(9999, $response['body']['$sequence']); + if ($this->getSide() === 'client') { + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']); + } else { + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']); + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']); + } + return []; } From 8834163c91378cd46592f30207e22068daec2c52 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 29 Aug 2025 21:01:09 +1200 Subject: [PATCH 4/5] Fix tests --- .../Databases/Legacy/DatabasesBase.php | 4 +- .../Legacy/DatabasesCustomClientTest.php | 153 ----------------- .../Databases/TablesDB/DatabasesBase.php | 5 +- .../TablesDB/DatabasesCustomClientTest.php | 154 ------------------ 4 files changed, 6 insertions(+), 310 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index fbd0714d26..59864255ab 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4341,7 +4341,9 @@ trait DatabasesBase ] ]); if ($this->getSide() === 'client') { - $this->assertEquals($document['headers']['status-code'], 400); + $this->assertEquals($document['body']['title'], 'Again Updated Date Test'); + $this->assertNotEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); + $this->assertNotEquals($document['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); } else { $this->assertEquals($document['body']['title'], 'Again Updated Date Test'); $this->assertEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php index 23153e8f39..699a2b8f25 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php @@ -889,157 +889,4 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); } - public function testModifyCreatedAtUpdatedAtSingleDocument(): void - { - $database = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database' - ]); - - $databaseId = $database['body']['$id']; - - $table = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Test Table', - 'documentsecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ], - ]); - $collectionId = $table['body']['$id']; - - // Create string column - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - - sleep(1); - - // Test 1: Try to create document with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Try to create document with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Try to create document with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 4: Create a valid document first - $validRow = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Valid Movie' - ] - ]); - - $this->assertEquals(201, $validRow['headers']['status-code']); - $documentId = $validRow['body']['$id']; - - // Test 5: Try to update document with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 6: Try to update document with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 7: Try to update document with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - } } diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index c57421c384..c2a1ee859d 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4269,9 +4269,10 @@ trait DatabasesBase ]); if ($this->getSide() === 'client') { - $this->assertEquals($row['headers']['status-code'], 400); - } else { $this->assertEquals($row['body']['title'], 'Again Updated Date Test'); + $this->assertNotEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); + $this->assertNotEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); + } else { $this->assertEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); $this->assertEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php index 277771e2df..f986b5dd03 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php @@ -890,158 +890,4 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); } - - public function testModifyCreatedAtUpdatedAtSingleRow(): void - { - $database = $this->client->call(Client::METHOD_POST, '/tablesdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database' - ]); - - $databaseId = $database['body']['$id']; - - $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'tableId' => ID::unique(), - 'name' => 'Test Table', - 'rowSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ], - ]); - $tableId = $table['body']['$id']; - - // Create string column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - - sleep(1); - - // Test 1: Try to create row with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Try to create row with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Try to create row with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 4: Create a valid row first - $validRow = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Valid Movie' - ] - ]); - - $this->assertEquals(201, $validRow['headers']['status-code']); - $rowId = $validRow['body']['$id']; - - // Test 5: Try to update row with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 6: Try to update row with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 7: Try to update row with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - } } From 1e230a93a9886d7811fd5dcc97fd95089782454a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 29 Aug 2025 21:06:13 +1200 Subject: [PATCH 5/5] Format --- .../Databases/Http/Databases/Collections/Documents/Action.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index c952b3bc6e..d1d0738990 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -212,8 +212,7 @@ abstract class Action extends AppwriteAction protected function removeReadonlyAttributes( Document|array $document, bool $privileged = false, - ): Document|array - { + ): Document|array { foreach ($this->removableAttributes['*'] as $attribute) { unset($document[$attribute]); }