diff --git a/app/config/events.php b/app/config/events.php index 8e759aaf56..c6006b569f 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, @@ -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, diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 85751811ba..4a968e63f2 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(), '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); } diff --git a/app/controllers/general.php b/app/controllers/general.php index 5accae6a32..0c2a20cadc 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -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(), 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 a0d261c518..4321d88110 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", @@ -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" } 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 7f67889a1c..31f17388b9 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\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()); } 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 b4246377ca..b87b4bdea1 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\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()); } 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 0332ca3673..8566911429 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 @@ -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); } 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 c5bcf5015e..b967394288 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 0902a850a7..d08c30e943 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) 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(), diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 1e637f8d81..2f300e7788 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,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']); } } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index ddc0af99c7..580f9144b6 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', @@ -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