Merge remote-tracking branch 'origin/1.8.x' into feat-txn

# Conflicts:
#	composer.lock
This commit is contained in:
Jake Barnby
2025-08-16 00:07:42 +12:00
14 changed files with 246 additions and 125 deletions
+35 -17
View File
@@ -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,
@@ -120,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.'
]
@@ -133,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,
@@ -156,12 +165,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,
@@ -172,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,
@@ -181,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.'
]
@@ -188,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,
+14 -4
View File
@@ -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(), 'Is the file stored in an 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);
}
+1 -1
View File
@@ -363,7 +363,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(),
+6 -6
View File
@@ -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.*",
Generated
+81 -73
View File
@@ -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",
@@ -758,23 +760,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,46 +801,48 @@
"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",
"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": {
@@ -887,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": {
@@ -912,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": {
@@ -956,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",
@@ -3545,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": {
@@ -3595,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",
@@ -8399,7 +8407,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -8423,5 +8431,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}
@@ -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\Document;
use Utopia\Database\Exception\Conflict as ConflictException;
@@ -44,10 +45,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)
@@ -168,18 +169,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());
}
@@ -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\Document;
use Utopia\Database\Exception\Conflict as ConflictException;
@@ -44,10 +45,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)
@@ -168,18 +169,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());
}
@@ -340,11 +340,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);
}
@@ -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)
@@ -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)
@@ -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(),
+2 -2
View File
@@ -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(),
@@ -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,25 +6927,34 @@ 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'],
]));
$this->assertEquals(404, $notFound['headers']['status-code']);
// Test decrement with value 0
$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'],
]), [
'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/rows/$rowId/count/decrement", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'value' => 0
]);
$this->assertEquals(400, $inc3['headers']['status-code']);
}
}
@@ -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',
@@ -5715,6 +5736,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
@@ -5836,6 +5866,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/$documentId/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 testCreateTransaction(): void