From 6043cc2fc9b92a5bd7147d6367144502713058a3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 17:34:56 +1200 Subject: [PATCH 01/16] Add upsert event --- app/config/events.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 0bfddf4f1f..d441874e2e 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -106,12 +106,15 @@ return [ 'create' => [ '$description' => 'This event triggers when a document is created.', ], - 'delete' => [ - '$description' => 'This event triggers when a document is deleted.' - ], 'update' => [ '$description' => 'This event triggers when a document is updated.' ], + 'upsert' => [ + '$description' => 'This event triggers when a document is upserted.', + ], + 'delete' => [ + '$description' => 'This event triggers when a document is deleted.' + ], ], 'indexes' => [ '$model' => Response::MODEL_INDEX, From 1e9d153110571b9bd314010dfc5c03fb82931e3a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:22:38 +1200 Subject: [PATCH 02/16] Fix uncaught invalid arg --- app/controllers/api/databases.php | 4 +++ composer.lock | 36 +++++++++---------- .../e2e/Services/Databases/DatabasesBase.php | 18 ++++++++++ 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index da99b053ad..bf78f4da7a 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -4552,6 +4552,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the maximum value of ' . $max); } catch (TypeException) { throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number'); + } catch (InvalidArgumentException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); } $queueForStatsUsage @@ -4629,6 +4631,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute "' . $attribute . '" has reached the minimum value of ' . $min); } catch (TypeException) { throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Attribute "' . $attribute . '" is not a number'); + } catch (InvalidArgumentException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); } $queueForStatsUsage diff --git a/composer.lock b/composer.lock index 6d1b376118..8a41a4e201 100644 --- a/composer.lock +++ b/composer.lock @@ -1228,16 +1228,16 @@ }, { "name": "open-telemetry/context", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc" + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", - "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", "shasum": "" }, "require": { @@ -1283,7 +1283,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-04T03:25:06+00:00" + "time": "2025-08-13T01:12:00+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -3545,16 +3545,16 @@ }, { "name": "utopia-php/database", - "version": "0.71.13", + "version": "0.71.14", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "43aaba72b5699a06351b25924c3417ff83e6bb4b" + "reference": "20998e268ec528ebbf06adb77ec4230f70469fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/43aaba72b5699a06351b25924c3417ff83e6bb4b", - "reference": "43aaba72b5699a06351b25924c3417ff83e6bb4b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/20998e268ec528ebbf06adb77ec4230f70469fa7", + "reference": "20998e268ec528ebbf06adb77ec4230f70469fa7", "shasum": "" }, "require": { @@ -3595,9 +3595,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.71.13" + "source": "https://github.com/utopia-php/database/tree/0.71.14" }, - "time": "2025-08-11T10:47:16+00:00" + "time": "2025-08-14T07:15:06+00:00" }, { "name": "utopia-php/detector", @@ -5440,16 +5440,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -5468,7 +5468,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -5492,9 +5492,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-07-27T20:03:57+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 9b3e31dd70..a7e9d2b24b 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -5569,6 +5569,15 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ])); $this->assertEquals(404, $notFound['headers']['status-code']); + + // Test increment with value 0 + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/collections/$collectionId/documents/$docId/count/increment", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'value' => 0 + ]); + $this->assertEquals(400, $inc3['headers']['status-code']); } public function testDecrementAttribute(): void @@ -5690,6 +5699,15 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ]), ['value' => 'not-a-number']); $this->assertEquals(400, $typeErr['headers']['status-code']); + + // Test decrement with value 0 + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/collections/$collectionId/documents/$docId/count/increment", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'value' => 0 + ]); + $this->assertEquals(400, $inc3['headers']['status-code']); } From b4b0d66af4c7e5cd6b999548a215615f43e5fe78 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:32:30 +1200 Subject: [PATCH 03/16] Fix test --- tests/e2e/Services/Databases/DatabasesBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index a7e9d2b24b..15a6722058 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -5701,7 +5701,7 @@ trait DatabasesBase $this->assertEquals(400, $typeErr['headers']['status-code']); // Test decrement with value 0 - $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/collections/$collectionId/documents/$docId/count/increment", array_merge([ + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/collections/$collectionId/documents/$documentId/count/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ]), [ From 8455ded051cc07e8e0e332206e2d468e91d1e3a5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:34:52 +1200 Subject: [PATCH 04/16] Fix events --- app/controllers/api/databases.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index bf78f4da7a..d3194bcdcf 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -4493,7 +4493,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment') ->desc('Increment document attribute') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].increment') + ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'documents.increment') @@ -4563,6 +4563,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $queueForEvents ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collectionId) + ->setParam('documentId', $document->getId()) ->setContext('collection', $collection) ->setContext('database', $database); @@ -4572,7 +4573,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/decrement') ->desc('Decrement document attribute') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].decrement') + ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'documents.decrement') @@ -4642,6 +4643,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $queueForEvents ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collectionId) + ->setParam('documentId', $document->getId()) ->setContext('collection', $collection) ->setContext('database', $database); From 4fcaeaec0ec85a5bea305726277159b150b72102 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 14 Aug 2025 18:10:00 +0530 Subject: [PATCH 05/16] Add internal file upload support and authorization checks in migrations --- app/controllers/api/migrations.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 85751811ba..aa4cb03ae8 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -35,6 +35,7 @@ use Utopia\Storage\Compression\Compression; use Utopia\Storage\Device; use Utopia\System\System; use Utopia\Validator\ArrayList; +use Utopia\Validator\Boolean; use Utopia\Validator\Integer; use Utopia\Validator\Text; use Utopia\Validator\URL; @@ -328,24 +329,33 @@ App::post('/v1/migrations/csv') ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.') + ->param('internalFile', false, new Boolean(), 'For uploading to internal bucket', true) ->inject('response') ->inject('dbForProject') + ->inject('dbForPlatform') ->inject('project') ->inject('deviceForFiles') ->inject('deviceForImports') ->inject('queueForEvents') ->inject('queueForMigrations') - ->action(function (string $bucketId, string $fileId, string $resourceId, Response $response, Database $dbForProject, Document $project, Device $deviceForFiles, Device $deviceForImports, Event $queueForEvents, Migration $queueForMigrations) { + ->action(function (string $bucketId, string $fileId, string $resourceId, bool $internalFile, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Device $deviceForFiles, Device $deviceForImports, Event $queueForEvents, Migration $queueForMigrations) { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + if ($internalFile && !$isPrivilegedUser) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + $bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) { + if ($internalFile) { + return $dbForPlatform->getDocument('buckets', 'default'); + } + return $dbForProject->getDocument('buckets', $bucketId); + }); if ($bucket->isEmpty() || (!$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); + $file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } From 64820ad5f50deb07f0357b6069194a7f5cb2d871 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:28:22 +1200 Subject: [PATCH 06/16] Port inc/dec fixes --- .../Documents/Attribute/Decrement.php | 22 +++++++++++++++---- .../Documents/Attribute/Increment.php | 22 +++++++++++++++---- .../Collections/Documents/Update.php | 2 +- .../Grids/Tables/Rows/Column/Decrement.php | 4 ++-- .../Grids/Tables/Rows/Column/Increment.php | 4 ++-- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php index 49a408e64c..a97e3b97e4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php @@ -12,6 +12,7 @@ use Appwrite\SDK\Deprecated; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; +use InvalidArgumentException; use Utopia\Database\Database; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Limit as LimitException; @@ -42,10 +43,10 @@ class Decrement extends Action ->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/decrement') ->desc('Decrement document attribute') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].decrement') + ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.decrement') + ->label('audits.event', 'documents.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) @@ -109,18 +110,31 @@ class Decrement extends Action throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the minimum value of ' . $min); } catch (TypeException) { throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, $this->getSdkNamespace() . ' "' . $attribute . '" is not a number'); + } catch (InvalidArgumentException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); } + $relationships = \array_map( + fn ($document) => $document->getAttribute('key'), + \array_filter( + $collection->getAttribute('attributes', []), + fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + ) + ); + $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); $queueForEvents ->setParam('databaseId', $databaseId) - ->setContext('database', $database) ->setParam('collectionId', $collectionId) ->setParam('tableId', $collectionId) - ->setContext($this->getCollectionsEventsContext(), $collection); + ->setParam('documentId', $documentId) + ->setParam('rowId', $documentId) + ->setContext('database', $database) + ->setContext($this->getCollectionsEventsContext(), $collection) + ->setPayload($response->getPayload(), sensitive: $relationships); $response->dynamic($document, $this->getResponseModel()); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php index 5eadc96b9e..e213a347d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php @@ -12,6 +12,7 @@ use Appwrite\SDK\Deprecated; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; +use InvalidArgumentException; use Utopia\Database\Database; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Limit as LimitException; @@ -42,10 +43,10 @@ class Increment extends Action ->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment') ->desc('Increment document attribute') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].increment') + ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.increment') + ->label('audits.event', 'documents.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) @@ -109,18 +110,31 @@ class Increment extends Action throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the maximum value of ' . $max); } catch (TypeException) { throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, $this->getSdkNamespace() . ' "' . $attribute . '" is not a number'); + } catch (InvalidArgumentException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); } + $relationships = \array_map( + fn ($document) => $document->getAttribute('key'), + \array_filter( + $collection->getAttribute('attributes', []), + fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + ) + ); + $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); $queueForEvents ->setParam('databaseId', $databaseId) - ->setContext('database', $database) ->setParam('collectionId', $collectionId) ->setParam('tableId', $collectionId) - ->setContext($this->getCollectionsEventsContext(), $collection); + ->setParam('documentId', $documentId) + ->setParam('rowId', $documentId) + ->setContext('database', $database) + ->setContext($this->getCollectionsEventsContext(), $collection) + ->setPayload($response->getPayload(), sensitive: $relationships); $response->dynamic($document, $this->getResponseModel()); } 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 1d06e6d0da..fe08d1ed85 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 @@ -280,11 +280,11 @@ class Update extends Action $queueForEvents ->setParam('databaseId', $databaseId) - ->setContext('database', $database) ->setParam('collectionId', $collection->getId()) ->setParam('tableId', $collection->getId()) ->setParam('documentId', $document->getId()) ->setParam('rowId', $document->getId()) + ->setContext('database', $database) ->setContext($this->getCollectionsEventsContext(), $collection) ->setPayload($response->getPayload(), sensitive: $relationships); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Decrement.php index 272510335f..fffd6db0fd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Decrement.php @@ -32,10 +32,10 @@ class Decrement extends DecrementDocumentAttribute ->setHttpPath('/v1/databases/:databaseId/grids/tables/:tableId/rows/:rowId/:column/decrement') ->desc('Decrement row column') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].decrement') + ->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].update') ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'rows.decrement') + ->label('audits.event', 'rows.update') ->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Increment.php index 2a28418a6e..67cbd81367 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Column/Increment.php @@ -32,10 +32,10 @@ class Increment extends IncrementDocumentAttribute ->setHttpPath('/v1/databases/:databaseId/grids/tables/:tableId/rows/:rowId/:column/increment') ->desc('Increment row column') ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].increment') + ->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].update') ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'rows.increment') + ->label('audits.event', 'rows.update') ->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) From 77af36a7233f8d5a09066ac1fbaf1b5638a4310a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:35:37 +1200 Subject: [PATCH 07/16] Port tests --- .../Databases/Grids/DatabasesBase.php | 18 ++++++++++++++++ .../Databases/Legacy/DatabasesBase.php | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 1e637f8d81..83680c493f 100644 --- a/tests/e2e/Services/Databases/Grids/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Grids/DatabasesBase.php @@ -6946,6 +6946,15 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ])); $this->assertEquals(404, $notFound['headers']['status-code']); + + // Test decrement with value 0 + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/documents/$rowId/count/decrement", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'value' => 0 + ]); + $this->assertEquals(400, $inc3['headers']['status-code']); } public function testDecrementColumn(): void @@ -7047,5 +7056,14 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ]), ['value' => 'not-a-number']); $this->assertEquals(400, $typeErr['headers']['status-code']); + + // Test decrement with value 0 + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/documents/$rowId/count/decrement", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'value' => 0 + ]); + $this->assertEquals(400, $inc3['headers']['status-code']); } } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 7dd4e8e3ab..1153f7ead7 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1501,6 +1501,27 @@ trait DatabasesBase $this->assertEquals('lengthTestIndex', $index['body']['key']); $this->assertEquals([128, 200], $index['body']['lengths']); + // Test case for lengths array overriding + // set a length for an array attribute, it should get overriden with Database::ARRAY_INDEX_LENGTH + $create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'key' => 'lengthOverrideTestIndex', + 'type' => 'key', + 'attributes' => ['actors'], + 'lengths' => [120] + ]); + $this->assertEquals(202, $create['headers']['status-code']); + + $index = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/indexes/lengthOverrideTestIndex", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]); + $this->assertEquals([Database::ARRAY_INDEX_LENGTH], $index['body']['lengths']); + // Test case for count of lengths greater than attributes (should throw 400) $create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [ 'content-type' => 'application/json', From de0b4dd73342858a591bc6a84e8e51aa1c0c46f0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:39:59 +1200 Subject: [PATCH 08/16] Lint + lock --- composer.lock | 31 ++++++++++--------- .../Collections/Documents/Create.php | 2 +- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/composer.lock b/composer.lock index b5f62642e7..3283cac3d7 100644 --- a/composer.lock +++ b/composer.lock @@ -758,23 +758,26 @@ }, { "name": "google/protobuf", - "version": "v4.31.1", + "version": "v4.32.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" + }, + "provide": { + "ext-protobuf": "*" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -796,9 +799,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" }, - "time": "2025-05-28T18:52:35+00:00" + "time": "2025-08-14T20:00:33+00:00" }, { "name": "league/csv", @@ -3545,16 +3548,16 @@ }, { "name": "utopia-php/database", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473" + "reference": "33f35f5daeebd587f79abf362cfb512b9df3cd50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/eaa4e275cefdeeb90bcece2f056e05b59f5b1473", - "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473", + "url": "https://api.github.com/repos/utopia-php/database/zipball/33f35f5daeebd587f79abf362cfb512b9df3cd50", + "reference": "33f35f5daeebd587f79abf362cfb512b9df3cd50", "shasum": "" }, "require": { @@ -3595,9 +3598,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.0.0" + "source": "https://github.com/utopia-php/database/tree/1.0.1" }, - "time": "2025-08-11T13:56:31+00:00" + "time": "2025-08-13T12:28:06+00:00" }, { "name": "utopia-php/detector", 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 bd46345a21..378ab8cfd0 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 @@ -413,7 +413,7 @@ class Create extends Action ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection $response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED); - + if ($isBulk) { $response->dynamic(new Document([ 'total' => count($documents), From 3bb5400939a5b2cb7c6177c3f854b4c86b5e2a66 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:49:52 +1200 Subject: [PATCH 09/16] Update 3rd aprty deps + fix tests --- composer.json | 12 +- composer.lock | 131 +++++++++--------- .../Databases/Grids/DatabasesBase.php | 14 +- 3 files changed, 81 insertions(+), 76 deletions(-) diff --git a/composer.json b/composer.json index c7b80f8621..0c662c775f 100644 --- a/composer.json +++ b/composer.json @@ -76,19 +76,19 @@ "utopia-php/telemetry": "0.1.*", "utopia-php/vcs": "0.11.*", "utopia-php/websocket": "0.3.*", - "matomo/device-detector": "6.1.*", - "dragonmantank/cron-expression": "3.3.*", + "matomo/device-detector": "6.4.*", + "dragonmantank/cron-expression": "3.4.*", "phpmailer/phpmailer": "6.9.*", - "chillerlan/php-qrcode": "4.3.*", + "chillerlan/php-qrcode": "4.4.*", "adhocore/jwt": "1.1.*", - "spomky-labs/otphp": "^10.0", + "spomky-labs/otphp": "10.0.*", "webonyx/graphql-php": "14.11.*", - "league/csv": "9.14.*", + "league/csv": "9.24.*", "enshrined/svg-sanitize": "0.22.*" }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "0.*.*", + "appwrite/sdk-generator": "*", "phpunit/phpunit": "9.*", "swoole/ide-helper": "5.1.2", "phpstan/phpstan": "1.8.*", diff --git a/composer.lock b/composer.lock index 3283cac3d7..3d49975acc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3456d5ee571287eb4ea90683279f209a", + "content-hash": "0da713ee5642eba1d30bc51c1a04a723", "packages": [ { "name": "adhocore/jwt", @@ -343,31 +343,34 @@ }, { "name": "chillerlan/php-qrcode", - "version": "4.3.4", + "version": "4.4.2", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d" + "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", - "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4", + "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4", "shasum": "" }, "require": { - "chillerlan/php-settings-container": "^2.1.4", + "chillerlan/php-settings-container": "^2.1.6 || ^3.2.1", "ext-mbstring": "*", "php": "^7.4 || ^8.0" }, "require-dev": { - "phan/phan": "^5.3", - "phpunit/phpunit": "^9.5", - "setasign/fpdf": "^1.8.2" + "phan/phan": "^5.4.5", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.11" }, "suggest": { "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", - "setasign/fpdf": "Required to use the QR FPDF output." + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" }, "type": "library", "autoload": { @@ -394,7 +397,7 @@ "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" } ], - "description": "A QR code generator. PHP 7.4+", + "description": "A QR code generator with a user friendly API. PHP 7.4+", "homepage": "https://github.com/chillerlan/php-qrcode", "keywords": [ "phpqrcode", @@ -405,43 +408,39 @@ ], "support": { "issues": "https://github.com/chillerlan/php-qrcode/issues", - "source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4" + "source": "https://github.com/chillerlan/php-qrcode/tree/4.4.2" }, "funding": [ - { - "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", - "type": "custom" - }, { "url": "https://ko-fi.com/codemasher", "type": "ko_fi" } ], - "time": "2022-07-25T09:12:45+00:00" + "time": "2024-11-15T15:36:24+00:00" }, { "name": "chillerlan/php-settings-container", - "version": "2.1.6", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "5553558bd381fce5108c6d0343c12e488cfec6bb" + "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/5553558bd381fce5108c6d0343c12e488cfec6bb", - "reference": "5553558bd381fce5108c6d0343c12e488cfec6bb", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681", + "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.4 || ^8.0" + "php": "^8.1" }, "require-dev": { "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^1.11", "phpstan/phpstan-deprecation-rules": "^1.2", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^10.5", "squizlabs/php_codesniffer": "^3.10" }, "type": "library", @@ -461,10 +460,9 @@ "homepage": "https://github.com/codemasher" } ], - "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+", + "description": "A container class for immutable settings objects. Not a DI container.", "homepage": "https://github.com/chillerlan/php-settings-container", "keywords": [ - "PHP7", "Settings", "configuration", "container", @@ -484,7 +482,7 @@ "type": "ko_fi" } ], - "time": "2024-07-17T01:04:28+00:00" + "time": "2024-07-16T11:13:48+00:00" }, { "name": "composer/semver", @@ -569,16 +567,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -591,10 +589,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -618,7 +620,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -626,7 +628,7 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "enshrined/svg-sanitize", @@ -805,40 +807,42 @@ }, { "name": "league/csv", - "version": "9.14.0", + "version": "9.24.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5" + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/34bf0df7340b60824b9449b5c526fcc3325070d5", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/e0221a3f16aa2a823047d59fab5809d552e29bc8", + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8", "shasum": "" }, "require": { "ext-filter": "*", - "ext-json": "*", - "ext-mbstring": "*", "php": "^8.1.2" }, "require-dev": { - "doctrine/collections": "^2.1.4", "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^v3.22.0", - "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.10.50", - "phpstan/phpstan-deprecation-rules": "^1.1.4", - "phpstan/phpstan-phpunit": "^1.3.15", - "phpstan/phpstan-strict-rules": "^1.5.2", - "phpunit/phpunit": "^10.5.3", - "symfony/var-dumper": "^6.4.0" + "friendsofphp/php-cs-fixer": "^3.75.0", + "phpbench/phpbench": "^1.4.1", + "phpstan/phpstan": "^1.12.27", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.2", + "phpstan/phpstan-strict-rules": "^1.6.2", + "phpunit/phpunit": "^10.5.16 || ^11.5.22", + "symfony/var-dumper": "^6.4.8 || ^7.3.0" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", - "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters", + "ext-mbstring": "Needed to ease transcoding CSV using mb stream filters", + "ext-mysqli": "Requiered to use the package with the MySQLi extension", + "ext-pdo": "Required to use the package with the PDO extension", + "ext-pgsql": "Requiered to use the package with the PgSQL extension", + "ext-sqlite3": "Required to use the package with the SQLite3 extension" }, "type": "library", "extra": { @@ -890,20 +894,20 @@ "type": "github" } ], - "time": "2023-12-29T07:34:53+00:00" + "time": "2025-06-25T14:53:51+00:00" }, { "name": "matomo/device-detector", - "version": "6.1.6", + "version": "6.4.6", "source": { "type": "git", "url": "https://github.com/matomo-org/device-detector.git", - "reference": "5cbea85106e561c7138d03603eb6e05128480409" + "reference": "6f07f615199851548db47a900815d2ea2cdcde08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/5cbea85106e561c7138d03603eb6e05128480409", - "reference": "5cbea85106e561c7138d03603eb6e05128480409", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/6f07f615199851548db47a900815d2ea2cdcde08", + "reference": "6f07f615199851548db47a900815d2ea2cdcde08", "shasum": "" }, "require": { @@ -915,11 +919,12 @@ }, "require-dev": { "matthiasmullie/scrapbook": "^1.4.7", - "mayflower/mo4-coding-standard": "^v8.0.0", - "phpstan/phpstan": "^0.12.52", + "mayflower/mo4-coding-standard": "^v9.0.0", + "phpstan/phpstan": "^1.10.44", "phpunit/phpunit": "^8.5.8", "psr/cache": "^1.0.1", "psr/simple-cache": "^1.0.1", + "slevomat/coding-standard": "<8.16.0", "symfony/yaml": "^5.1.7" }, "suggest": { @@ -959,7 +964,7 @@ "source": "https://github.com/matomo-org/matomo", "wiki": "https://dev.matomo.org/" }, - "time": "2023-10-02T10:01:54+00:00" + "time": "2025-06-10T10:00:59+00:00" }, { "name": "mustangostang/spyc", @@ -3548,16 +3553,16 @@ }, { "name": "utopia-php/database", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "33f35f5daeebd587f79abf362cfb512b9df3cd50" + "reference": "e194da3ff803c51486d343c88156619fec9d1531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/33f35f5daeebd587f79abf362cfb512b9df3cd50", - "reference": "33f35f5daeebd587f79abf362cfb512b9df3cd50", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e194da3ff803c51486d343c88156619fec9d1531", + "reference": "e194da3ff803c51486d343c88156619fec9d1531", "shasum": "" }, "require": { @@ -3598,9 +3603,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.0.1" + "source": "https://github.com/utopia-php/database/tree/1.0.2" }, - "time": "2025-08-13T12:28:06+00:00" + "time": "2025-08-15T07:11:01+00:00" }, { "name": "utopia-php/detector", diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 83680c493f..3c712abde8 100644 --- a/tests/e2e/Services/Databases/Grids/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Grids/DatabasesBase.php @@ -6900,10 +6900,10 @@ trait DatabasesBase ]); $this->assertEquals(201, $doc['headers']['status-code']); - $docId = $doc['body']['$id']; + $rowId = $doc['body']['$id']; // Increment by default 1 - $inc = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$docId/count/increment", array_merge([ + $inc = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/count/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ])); @@ -6911,14 +6911,14 @@ trait DatabasesBase $this->assertEquals(6, $inc['body']['count']); // Verify count = 6 - $get = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/grids/tables/$tableId/rows/$docId", array_merge([ + $get = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(6, $get['body']['count']); // Increment by custom value 4 - $inc2 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$docId/count/increment", array_merge([ + $inc2 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/count/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ]), [ @@ -6927,21 +6927,21 @@ trait DatabasesBase $this->assertEquals(200, $inc2['headers']['status-code']); $this->assertEquals(10, $inc2['body']['count']); - $get2 = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/grids/tables/$tableId/rows/$docId", array_merge([ + $get2 = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(10, $get2['body']['count']); // Test max limit exceeded - $err = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$docId/count/increment", array_merge([ + $err = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/count/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ]), ['max' => 8]); $this->assertEquals(400, $err['headers']['status-code']); // Test attribute not found - $notFound = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$docId/unknown/increment", array_merge([ + $notFound = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/unknown/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ])); From c69ec97802f1ab3d2e6abc63f81fbb0e54d092f4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:51:42 +1200 Subject: [PATCH 10/16] Fix index delete event --- .../Databases/Http/Databases/Collections/Indexes/Delete.php | 2 +- .../Modules/Databases/Http/Grids/Tables/Indexes/Delete.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php index 80ccd8fadf..5b4870205d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php @@ -42,7 +42,7 @@ class Delete extends Action ->groups(['api', 'database']) ->label('scope', 'collections.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') + ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php index 679de9b77c..2faafdf9e4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php @@ -37,7 +37,7 @@ class Delete extends IndexDelete ->groups(['api', 'database']) ->label('scope', 'tables.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update') + ->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].delete') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}') ->label('sdk', new Method( From a5e9fbf362ff94b26e5a2d82a033aa5c32ab0448 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 19:54:01 +1200 Subject: [PATCH 11/16] Add grids upsert event --- app/config/events.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 78e800ada0..7d676d6241 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -106,12 +106,15 @@ return [ 'create' => [ '$description' => 'This event triggers when a row is created.', ], - 'delete' => [ - '$description' => 'This event triggers when a row is deleted.' - ], 'update' => [ '$description' => 'This event triggers when a row is updated.' ], + 'upsert' => [ + '$description' => 'This event triggers when a document is upserted.', + ], + 'delete' => [ + '$description' => 'This event triggers when a row is deleted.' + ], ], 'indexes' => [ '$model' => Response::MODEL_COLUMN_INDEX, From 71ed39e17e428933c7c1fe3780a2d89692e081d0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 20:01:06 +1200 Subject: [PATCH 12/16] Revert "Fix index delete event" This reverts commit c69ec97802f1ab3d2e6abc63f81fbb0e54d092f4. --- .../Databases/Http/Databases/Collections/Indexes/Delete.php | 2 +- .../Modules/Databases/Http/Grids/Tables/Indexes/Delete.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php index 5b4870205d..80ccd8fadf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php @@ -42,7 +42,7 @@ class Delete extends Action ->groups(['api', 'database']) ->label('scope', 'collections.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete') + ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php index 2faafdf9e4..679de9b77c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Indexes/Delete.php @@ -37,7 +37,7 @@ class Delete extends IndexDelete ->groups(['api', 'database']) ->label('scope', 'tables.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].delete') + ->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}') ->label('sdk', new Method( From dace50d9b722a5a4ea11177a4a64801644cdd15d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 20:03:38 +1200 Subject: [PATCH 13/16] Add missing attribute/index update events --- app/config/events.php | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 7d676d6241..c6006b569f 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -123,6 +123,9 @@ return [ 'create' => [ '$description' => 'This event triggers when an index is created.', ], + 'update' => [ + '$description' => 'This event triggers when an index is updated.', + ], 'delete' => [ '$description' => 'This event triggers when an index is deleted.' ] @@ -136,17 +139,20 @@ return [ ], 'delete' => [ '$description' => 'This event triggers when an column is deleted.' - ] + ], + 'update' => [ + '$description' => 'This event triggers when a column is created.', + ], ], 'create' => [ '$description' => 'This event triggers when a table is created.' ], + 'update' => [ + '$description' => 'This event triggers when a table is updated.', + ], 'delete' => [ '$description' => 'This event triggers when a table is deleted.', ], - 'update' => [ - '$description' => 'This event triggers when a table is updated.', - ] ], 'collections' => [ '$model' => Response::MODEL_COLLECTION, @@ -178,7 +184,10 @@ return [ ], 'delete' => [ '$description' => 'This event triggers when an index is deleted.' - ] + ], + 'update' => [ + '$description' => 'This event triggers when a column is created.', + ], ], 'attributes' => [ '$model' => Response::MODEL_ATTRIBUTE, @@ -187,6 +196,9 @@ return [ 'create' => [ '$description' => 'This event triggers when an attribute is created.', ], + 'update' => [ + '$description' => 'This event triggers when a column is created.', + ], 'delete' => [ '$description' => 'This event triggers when an attribute is deleted.' ] @@ -194,22 +206,22 @@ return [ 'create' => [ '$description' => 'This event triggers when a collection is created.' ], + 'update' => [ + '$description' => 'This event triggers when a collection is updated.', + ], 'delete' => [ '$description' => 'This event triggers when a collection is deleted.', ], - 'update' => [ - '$description' => 'This event triggers when a collection is updated.', - ] ], 'create' => [ '$description' => 'This event triggers when a database is created.' ], + 'update' => [ + '$description' => 'This event triggers when a database is updated.', + ], 'delete' => [ '$description' => 'This event triggers when a database is deleted.', ], - 'update' => [ - '$description' => 'This event triggers when a database is updated.', - ] ], 'buckets' => [ '$model' => Response::MODEL_BUCKET, From 3871833b84b82c679c7b32555c361a7085029f30 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 20:18:37 +1200 Subject: [PATCH 14/16] Fix path --- tests/e2e/Services/Databases/Grids/DatabasesBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 3c712abde8..2f300e7788 100644 --- a/tests/e2e/Services/Databases/Grids/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Grids/DatabasesBase.php @@ -6948,7 +6948,7 @@ trait DatabasesBase $this->assertEquals(404, $notFound['headers']['status-code']); // Test decrement with value 0 - $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/documents/$rowId/count/decrement", array_merge([ + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/count/increment", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ]), [ @@ -7058,7 +7058,7 @@ trait DatabasesBase $this->assertEquals(400, $typeErr['headers']['status-code']); // Test decrement with value 0 - $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/documents/$rowId/count/decrement", array_merge([ + $inc3 = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/grids/tables/$tableId/rows/$rowId/count/decrement", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ]), [ From bc7e240626b41d55d165e3009d88ad9336095d03 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k <83803257+ArnabChatterjee20k@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:04:16 +0530 Subject: [PATCH 15/16] updated the the description of the internal files Co-authored-by: Jake Barnby --- app/controllers/api/migrations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index aa4cb03ae8..4a968e63f2 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -329,7 +329,7 @@ App::post('/v1/migrations/csv') ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.') - ->param('internalFile', false, new Boolean(), 'For uploading to internal bucket', true) + ->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') From 23bc39254c5cfb8c748e16e02ee938c6b4a87f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 15 Aug 2025 12:29:34 +0200 Subject: [PATCH 16/16] Increase dynamic API key expiration --- app/controllers/general.php | 2 +- composer.lock | 23 +++++++++++-------- .../Functions/Http/Executions/Create.php | 4 ++-- src/Appwrite/Platform/Workers/Functions.php | 4 ++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index d5b0979441..40f861ad8c 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -361,7 +361,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw $headers['x-appwrite-continent-code'] = ''; $headers['x-appwrite-continent-eu'] = 'false'; - $jwtExpiry = $resource->getAttribute('timeout', 900); + $jwtExpiry = $resource->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); $jwtKey = $jwtObj->encode([ 'projectId' => $project->getId(), diff --git a/composer.lock b/composer.lock index 8a41a4e201..cd3eb5fef7 100644 --- a/composer.lock +++ b/composer.lock @@ -758,23 +758,26 @@ }, { "name": "google/protobuf", - "version": "v4.31.1", + "version": "v4.32.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" + }, + "provide": { + "ext-protobuf": "*" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -796,9 +799,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" }, - "time": "2025-05-28T18:52:35+00:00" + "time": "2025-08-14T20:00:33+00:00" }, { "name": "league/csv", @@ -8399,7 +8402,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8423,5 +8426,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 905acf15df..ef31d5a79c 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -205,7 +205,7 @@ class Create extends Base } if (!$current->isEmpty()) { - $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); $jwt = $jwtObj->encode([ 'userId' => $user->getId(), @@ -214,7 +214,7 @@ class Create extends Base } } - $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); $apiKey = $jwtObj->encode([ 'projectId' => $project->getId(), diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 2c9ca16b0c..2e25248d9a 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -101,7 +101,7 @@ class Functions extends Action } if (empty($jwt) && !$user->isEmpty()) { - $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); $jwt = $jwtObj->encode([ 'userId' => $user->getId(), @@ -390,7 +390,7 @@ class Functions extends Action $runtime = $runtimes[$function->getAttribute('runtime')]; - $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); $apiKey = $jwtObj->encode([ 'projectId' => $project->getId(),