From aa36d944dbbd1b57c5c7eb637e36749896b15632 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 Mar 2025 22:00:37 +0100 Subject: [PATCH 001/385] Add teamId to project array in e2e test --- tests/e2e/Scopes/ProjectCustom.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index f2617bd4be..d5c10481e6 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -146,6 +146,7 @@ trait ProjectCustom $project = [ '$id' => $project['body']['$id'], 'name' => $project['body']['name'], + 'teamId' => $team['body']['$id'], 'apiKey' => $key['body']['secret'], 'webhookId' => $webhook['body']['$id'], 'signatureKey' => $webhook['body']['signatureKey'], From cb05b87dda13e8deb6c311b76095ba80ad2d9ddd Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 10 Mar 2025 00:08:04 +0100 Subject: [PATCH 002/385] Upgraded users to use utopia/auth --- app/controllers/api/users.php | 112 ++- app/controllers/shared/api.php | 69 +- app/init.php | 19 + composer.json | 1 + composer.lock | 210 +++--- src/Appwrite/Platform/Workers/Builds.php | 829 +++++++++++++++++++++++ 6 files changed, 1122 insertions(+), 118 deletions(-) create mode 100644 src/Appwrite/Platform/Workers/Builds.php diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 962022927f..4a0c6013ed 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -28,6 +28,7 @@ use Appwrite\Utopia\Response; use MaxMind\Db\Reader; use Utopia\App; use Utopia\Audit\Audit; +use Utopia\Auth\Hash; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -53,12 +54,20 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +use Utopia\Auth\Hashes\Argon2; +use Utopia\Auth\Hashes\Bcrypt; +use Utopia\Auth\Hashes\MD5; +use Utopia\Auth\Hashes\PHPass; +use Utopia\Auth\Hashes\Scrypt; +use Utopia\Auth\Hashes\ScryptModified; +use Utopia\Auth\Hashes\Sha; +use Utopia\Auth\Hashes\Plaintext; +use Utopia\Auth\Proofs\Password as ProofsPassword; /** TODO: Remove function when we move to using utopia/platform */ -function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document +function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document { $plaintextPassword = $password; - $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; if (!empty($email)) { @@ -92,7 +101,18 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e } } - $password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null; + $hashedPassword = null; + + if (!empty($password)) { + if ($hash instanceof Plaintext) { // Password was never hashed, hash it with the default hash + $defaultHash = new ProofsPassword(); + $hashedPassword = $defaultHash->hash($password); + $hash = $defaultHash->getHash(); + } else { + $hashedPassword = $password; + } + } + $user = new Document([ '$id' => $userId, '$permissions' => [ @@ -106,11 +126,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'phoneVerification' => false, 'status' => true, 'labels' => [], - 'password' => $password, - 'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password], - 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, - 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, - 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash], + 'password' => $hashedPassword, + 'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword], + 'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null, + 'hash' => $hash->getName(), + 'hashOptions' => $hash->getOptions(), 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -121,7 +141,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'search' => implode(' ', [$userId, $email, $phone, $name]), ]); - if ($hash === 'plaintext') { + if ($hash instanceof Plaintext) { $hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]); } @@ -211,7 +231,9 @@ App::post('/v1/users') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); + $plaintext = new Plaintext(); + + $user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -244,7 +266,10 @@ App::post('/v1/users/bcrypt') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $bcrypt = new Bcrypt(); + $bcrypt->setCost(8); // Default cost + + $user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -278,7 +303,9 @@ App::post('/v1/users/md5') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $md5 = new MD5(); + + $user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -312,7 +339,13 @@ App::post('/v1/users/argon2') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $argon2 = new Argon2(); + $argon2 + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); + + $user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -347,13 +380,12 @@ App::post('/v1/users/sha') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $options = '{}'; - + $sha = new Sha(); if (!empty($passwordVersion)) { - $options = '{"version":"' . $passwordVersion . '"}'; + $sha->setVersion($passwordVersion); } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -387,7 +419,9 @@ App::post('/v1/users/phpass') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $phpass = new PHPass(); + + $user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -426,15 +460,15 @@ App::post('/v1/users/scrypt') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $options = [ - 'salt' => $passwordSalt, - 'costCpu' => $passwordCpu, - 'costMemory' => $passwordMemory, - 'costParallel' => $passwordParallel, - 'length' => $passwordLength - ]; + $scrypt = new Scrypt(); + $scrypt + ->setSalt($passwordSalt) + ->setCpuCost($passwordCpu) + ->setMemoryCost($passwordMemory) + ->setParallelCost($passwordParallel) + ->setLength($passwordLength); - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -471,7 +505,13 @@ App::post('/v1/users/scrypt-modified') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $scryptModified = new ScryptModified(); + $scryptModified + ->setSalt($passwordSalt) + ->setSaltSeparator($passwordSaltSeparator) + ->setSignerKey($passwordSignerKey); + + $user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1012,7 +1052,6 @@ App::get('/v1/users/identities') } catch (QueryException $e) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } - if (!empty($search)) { $queries[] = Query::search('search', $search); } @@ -1269,7 +1308,13 @@ App::patch('/v1/users/:userId/password') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + // Create Argon2 hasher with default settings + $hasher = new Argon2(); + $hasher->setMemoryCost(65536); + $hasher->setTimeCost(4); + $hasher->setThreads(3); + + $newPassword = $hasher->hash($password); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $user->getAttribute('passwordHistory', []); @@ -1287,8 +1332,12 @@ App::patch('/v1/users/:userId/password') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); + ->setAttribute('hash', 'argon2') + ->setAttribute('hashOptions', [ + 'memoryCost' => 65536, + 'timeCost' => 4, + 'threads' => 3 + ]); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -2466,3 +2515,4 @@ App::get('/v1/users/usage') 'sessions' => $usage[$metrics[1]]['data'], ]), Response::MODEL_USAGE_USERS); }); + diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f8371ed8e6..f87ddf9730 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -200,33 +200,88 @@ App::init() ->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) { $route = $utopia->getRoute(); + /** + * Handle user authentication and session validation. + * + * This function follows a series of steps to determine the appropriate user session + * based on cookies, headers, and JWT tokens. + * + * Process: + * + * Project & Role Validation: + * 1. Check if the project is empty. If so, throw an exception. + * 2. Get the roles configuration. + * 3. Determine the role for the user based on the user document. + * 4. Get the scopes for the role. + * + * API Key Authentication: + * 5. If there is an API key: + * - Verify no user session exists simultaneously + * - Check if key is expired + * - Set role and scopes from API key + * - Handle special app role case + * - For standard keys, update last accessed time + * + * User Activity: + * 6. If the project is not the console and user is not admin: + * - Update user's last activity timestamp + * + * Access Control: + * 7. Get the method from the route + * 8. Validate namespace permissions + * 9. Validate scope permissions + * 10. Check if user is blocked + * + * Security Checks: + * 11. Verify password status (check if reset required) + * 12. Validate MFA requirements: + * - Check if MFA is enabled + * - Verify email status + * - Verify phone status + * - Verify authenticator status + * 13. Handle Multi-Factor Authentication: + * - Check remaining required factors + * - Validate factor completion + * - Throw exception if factors incomplete + */ + + // Step 1: Check if project is empty if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } + // Step 2: Get roles configuration $roles = Config::getParam('roles', []); + // Step 3: Determine role for user + // TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token. + $role = $user->isEmpty() ? Role::guests()->toString() : Role::users()->toString(); + // Step 4: Get scopes for the role $scopes = $roles[$role]['scopes']; - // API Key authentication + // Step 5: API Key Authentication if (!empty($apiKey)) { + // Verify no user session exists simultaneously if (!$user->isEmpty()) { throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET); } + // Check if key is expired if ($apiKey->isExpired()) { throw new Exception(Exception::PROJECT_KEY_EXPIRED); } + // Set role and scopes from API key $role = $apiKey->getRole(); $scopes = $apiKey->getScopes(); // Disable authorization checks for API keys Authorization::setDefaultStatus(false); + // Handle special app role case if ($apiKey->getRole() === Auth::USER_ROLE_APPS) { $user = new Document([ '$id' => '', @@ -240,6 +295,7 @@ App::init() $queueForAudits->setUser($user); } + // For standard keys, update last accessed time if ($apiKey->getType() === API_KEY_STANDARD) { $dbKey = $project->find( key: 'secret', @@ -307,7 +363,7 @@ App::init() Authorization::setRole($authRole); } - // Update project last activity + // Step 6: Update project and user last activity if (!$project->isEmpty() && $project->getId() !== 'console') { $accessedAt = $project->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { @@ -316,7 +372,6 @@ App::init() } } - // Update user last activity if (!empty($user->getId())) { $accessedAt = $user->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) { @@ -330,6 +385,7 @@ App::init() } } + // Steps 7-9: Access Control - Method, Namespace and Scope Validation /** * @var ?Method $method */ @@ -351,21 +407,23 @@ App::init() } } - // Do now allow access if scope is not allowed + // Step 9: Validate scope permissions $scope = $route->getLabel('scope', 'none'); if (!\in_array($scope, $scopes)) { throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')'); } - // Do not allow access to blocked accounts + // Step 10: Check if user is blocked if (false === $user->getAttribute('status')) { // Account is blocked throw new Exception(Exception::USER_BLOCKED); } + // Step 11: Verify password status if ($user->getAttribute('reset')) { throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED); } + // Step 12: Validate MFA requirements $mfaEnabled = $user->getAttribute('mfa', false); $hasVerifiedEmail = $user->getAttribute('emailVerification', false); $hasVerifiedPhone = $user->getAttribute('phoneVerification', false); @@ -373,6 +431,7 @@ App::init() $hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator; $minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1; + // Step 13: Handle Multi-Factor Authentication if (!in_array('mfa', $route->getGroups())) { if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) { throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED); diff --git a/app/init.php b/app/init.php index 4e36c91cb1..324c9cca9a 100644 --- a/app/init.php +++ b/app/init.php @@ -1289,6 +1289,25 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons /** @var Utopia\Database\Database $dbForPlatform */ /** @var string $mode */ + /** + * Handles user authentication and session validation. + * + * This function follows a series of steps to determine the appropriate user session + * based on cookies, headers, and JWT tokens. + * + * Process: + * 1. Checks the cookie based on mode: + * - If in admin mode, redirects to the console. + * - Otherwise, retrieves the project ID from the cookie. + * 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`. + * - If this method is used, returns the header: `X-Debug-Fallback: true`. + * 3. Fetches the user document from the appropriate database based on the mode. + * 4. If the user document is empty or the session key cannot be verified, sets an empty user document. + * 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token. + * 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`, + * overwriting the previous value. + */ + Authorization::setDefaultStatus(true); Auth::setCookieName('a_session_' . $project->getId()); diff --git a/composer.json b/composer.json index 5f40f4d593..f7510c21ce 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "appwrite/php-runtimes": "0.17.*", "appwrite/php-clamav": "2.0.*", "utopia-php/abuse": "0.50.*", + "utopia-php/auth": "dev-dev", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.51.*", "utopia-php/cache": "0.11.*", diff --git a/composer.lock b/composer.lock index 3690e25ed0..992fad6dbf 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": "6883b3e81cfb0c5355997def668d5df2", + "content-hash": "e4697fc3967676a20b4891042adb391a", "packages": [ { "name": "adhocore/jwt", @@ -279,16 +279,16 @@ }, { "name": "brick/math", - "version": "0.12.2", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -327,7 +327,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.2" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -335,7 +335,7 @@ "type": "github" } ], - "time": "2025-02-26T10:21:45+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "chillerlan/php-qrcode", @@ -709,16 +709,16 @@ }, { "name": "google/protobuf", - "version": "v4.29.3", + "version": "v4.30.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7" + "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", - "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/e1d66682f6836aa87820400f0aa07d9eb566feb6", + "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6", "shasum": "" }, "require": { @@ -747,9 +747,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.3" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.0" }, - "time": "2025-01-08T21:00:13+00:00" + "time": "2025-03-04T22:54:49+00:00" }, { "name": "jean85/pretty-package-versions", @@ -1237,16 +1237,16 @@ }, { "name": "open-telemetry/api", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "8b925df3047628968bc5be722468db1b98b82d51" + "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/8b925df3047628968bc5be722468db1b98b82d51", - "reference": "8b925df3047628968bc5be722468db1b98b82d51", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", + "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", "shasum": "" }, "require": { @@ -1303,7 +1303,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-02-03T21:49:11+00:00" + "time": "2025-03-05T21:42:54+00:00" }, { "name": "open-telemetry/context", @@ -1366,16 +1366,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "243d9657c44a06f740cf384f486afe954c2b725f" + "reference": "b7580440b7481a98da97aceabeb46e1b276c8747" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/243d9657c44a06f740cf384f486afe954c2b725f", - "reference": "243d9657c44a06f740cf384f486afe954c2b725f", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/b7580440b7481a98da97aceabeb46e1b276c8747", + "reference": "b7580440b7481a98da97aceabeb46e1b276c8747", "shasum": "" }, "require": { @@ -1426,7 +1426,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-08T23:50:03+00:00" + "time": "2025-03-06T23:21:56+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -2371,16 +2371,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", "shasum": "" }, "require": { @@ -2388,25 +2388,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -2444,19 +2441,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-02T04:48:29+00:00" }, { "name": "ramsey/uuid", @@ -3519,6 +3506,61 @@ }, "time": "2025-02-12T09:12:44+00:00" }, + { + "name": "utopia-php/auth", + "version": "dev-dev", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/auth.git", + "reference": "b063a2317c48cc6f3dba1eab0298641b19accdcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/b063a2317c48cc6f3dba1eab0298641b19accdcd", + "reference": "b063a2317c48cc6f3dba1eab0298641b19accdcd", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-scrypt": "*", + "ext-sodium": "*", + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Auth\\": "src/Auth" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Utopia PHP", + "email": "team@appwrite.io" + } + ], + "description": "A simple PHP authentication library", + "keywords": [ + "Authentication", + "auth", + "php", + "security" + ], + "support": { + "issues": "https://github.com/utopia-php/auth/issues", + "source": "https://github.com/utopia-php/auth/tree/dev" + }, + "time": "2025-03-09T22:50:59+00:00" + }, { "name": "utopia-php/cache", "version": "0.11.0", @@ -3880,16 +3922,16 @@ }, { "name": "utopia-php/fetch", - "version": "0.3.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/fetch.git", - "reference": "02b12c05aec13399dcc2da8d51f908e328ab63f4" + "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/02b12c05aec13399dcc2da8d51f908e328ab63f4", - "reference": "02b12c05aec13399dcc2da8d51f908e328ab63f4", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", + "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", "shasum": "" }, "require": { @@ -3913,22 +3955,22 @@ "description": "A simple library that provides an interface for making HTTP Requests.", "support": { "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.3.0" + "source": "https://github.com/utopia-php/fetch/tree/0.3.1" }, - "time": "2025-01-17T06:11:10+00:00" + "time": "2025-03-05T18:08:55+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.17", + "version": "0.33.19", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644" + "reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/73fac6fbce9f56282dba4e52a58cf836ec434644", - "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644", + "url": "https://api.github.com/repos/utopia-php/http/zipball/64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0", + "reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0", "shasum": "" }, "require": { @@ -3960,9 +4002,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.17" + "source": "https://github.com/utopia-php/http/tree/0.33.19" }, - "time": "2025-02-24T17:35:48+00:00" + "time": "2025-03-06T11:37:49+00:00" }, { "name": "utopia-php/image", @@ -4607,22 +4649,24 @@ }, { "name": "utopia-php/storage", - "version": "0.18.9", + "version": "0.18.10", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "1cf455404e8700b3093fd73d74a38d41cdced90c" + "reference": "76f31158f4251abb207f7a9b16f7cb0bfdb3b39e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/1cf455404e8700b3093fd73d74a38d41cdced90c", - "reference": "1cf455404e8700b3093fd73d74a38d41cdced90c", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/76f31158f4251abb207f7a9b16f7cb0bfdb3b39e", + "reference": "76f31158f4251abb207f7a9b16f7cb0bfdb3b39e", "shasum": "" }, "require": { "ext-brotli": "*", + "ext-curl": "*", "ext-fileinfo": "*", "ext-lz4": "*", + "ext-simplexml": "*", "ext-snappy": "*", "ext-xz": "*", "ext-zlib": "*", @@ -4656,9 +4700,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.9" + "source": "https://github.com/utopia-php/storage/tree/0.18.10" }, - "time": "2025-02-11T13:10:40+00:00" + "time": "2025-03-03T10:47:54+00:00" }, { "name": "utopia-php/swoole", @@ -5051,16 +5095,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.1", + "version": "0.40.2", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "df180676b6fbde7832ae1495af3e2f3e8f700837" + "reference": "56f09482d9e2f223911277ab887f197402708049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/df180676b6fbde7832ae1495af3e2f3e8f700837", - "reference": "df180676b6fbde7832ae1495af3e2f3e8f700837", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/56f09482d9e2f223911277ab887f197402708049", + "reference": "56f09482d9e2f223911277ab887f197402708049", "shasum": "" }, "require": { @@ -5096,9 +5140,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.1" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.2" }, - "time": "2025-02-26T07:07:10+00:00" + "time": "2025-03-06T16:31:03+00:00" }, { "name": "doctrine/annotations", @@ -8806,7 +8850,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/auth": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8830,5 +8876,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php new file mode 100644 index 0000000000..c9833adcfb --- /dev/null +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -0,0 +1,829 @@ +desc('Builds worker') + ->inject('message') + ->inject('project') + ->inject('dbForPlatform') + ->inject('queueForEvents') + ->inject('queueForFunctions') + ->inject('queueForStatsUsage') + ->inject('cache') + ->inject('dbForProject') + ->inject('deviceForFunctions') + ->inject('isResourceBlocked') + ->inject('log') + ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) => + $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log)); + } + + /** + * @param Message $message + * @param Document $project + * @param Database $dbForPlatform + * @param Event $queueForEvents + * @param Func $queueForFunctions + * @param StatsUsage $queueForStatsUsage + * @param Cache $cache + * @param Database $dbForProject + * @param Device $deviceForFunctions + * @param Log $log + * @return void + * @throws \Utopia\Database\Exception + */ + public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void + { + $payload = $message->getPayload() ?? []; + + if (empty($payload)) { + throw new \Exception('Missing payload'); + } + + $type = $payload['type'] ?? ''; + $resource = new Document($payload['resource'] ?? []); + $deployment = new Document($payload['deployment'] ?? []); + $template = new Document($payload['template'] ?? []); + + $log->addTag('projectId', $project->getId()); + $log->addTag('type', $type); + + switch ($type) { + case BUILD_TYPE_DEPLOYMENT: + case BUILD_TYPE_RETRY: + Console::info('Creating build for deployment: ' . $deployment->getId()); + $github = new GitHub($cache); + $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log); + break; + + default: + throw new \Exception('Invalid build type'); + } + } + + /** + * @param Device $deviceForFunctions + * @param Func $queueForFunctions + * @param Event $queueForEvents + * @param StatsUsage $queueForStatsUsage + * @param Database $dbForPlatform + * @param Database $dbForProject + * @param GitHub $github + * @param Document $project + * @param Document $function + * @param Document $deployment + * @param Document $template + * @param Log $log + * @return void + * @throws \Utopia\Database\Exception + * @throws Exception + */ + protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void + { + $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); + + $functionId = $function->getId(); + $log->addTag('functionId', $function->getId()); + + $function = $dbForProject->getDocument('functions', $functionId); + if ($function->isEmpty()) { + throw new \Exception('Function not found'); + } + + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $functionId)) { + throw new \Exception('Function blocked'); + } + + $deploymentId = $deployment->getId(); + $log->addTag('deploymentId', $deploymentId); + + $deployment = $dbForProject->getDocument('deployments', $deploymentId); + if ($deployment->isEmpty()) { + throw new \Exception('Deployment not found'); + } + + if (empty($deployment->getAttribute('entrypoint', ''))) { + throw new \Exception('Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".'); + } + + $version = $function->getAttribute('version', 'v2'); + $spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)]; + $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); + $key = $function->getAttribute('runtime'); + $runtime = $runtimes[$key] ?? null; + if (\is_null($runtime)) { + throw new \Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); + } + + // Realtime preparation + $allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [ + 'functionId' => $function->getId(), + 'deploymentId' => $deployment->getId() + ]); + + $startTime = DateTime::now(); + $durationStart = \microtime(true); + $buildId = $deployment->getAttribute('buildId', ''); + $build = $dbForProject->getDocument('builds', $buildId); + $isNewBuild = empty($buildId); + if ($build->isEmpty()) { + $buildId = ID::unique(); + $build = $dbForProject->createDocument('builds', new Document([ + '$id' => $buildId, + '$permissions' => [], + 'startTime' => $startTime, + 'deploymentInternalId' => $deployment->getInternalId(), + 'deploymentId' => $deployment->getId(), + 'status' => 'processing', + 'path' => '', + 'runtime' => $function->getAttribute('runtime'), + 'source' => $deployment->getAttribute('path', ''), + 'sourceType' => strtolower($deviceForFunctions->getType()), + 'logs' => '', + 'endTime' => null, + 'duration' => 0, + 'size' => 0 + ])); + + $deployment->setAttribute('buildId', $build->getId()); + $deployment->setAttribute('buildInternalId', $build->getInternalId()); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); + } elseif ($build->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } else { + $build = $dbForProject->getDocument('builds', $buildId); + } + + $source = $deployment->getAttribute('path', ''); + $installationId = $deployment->getAttribute('installationId', ''); + $providerRepositoryId = $deployment->getAttribute('providerRepositoryId', ''); + $providerCommitHash = $deployment->getAttribute('providerCommitHash', ''); + $isVcsEnabled = !empty($providerRepositoryId); + $owner = ''; + $repositoryName = ''; + + if ($isVcsEnabled) { + $installation = $dbForPlatform->getDocument('installations', $installationId); + $providerInstallationId = $installation->getAttribute('providerInstallationId'); + $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); + $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); + + $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); + } + + try { + if ($isNewBuild && !$isVcsEnabled) { + // Non-VCS + Template + $templateRepositoryName = $template->getAttribute('repositoryName', ''); + $templateOwnerName = $template->getAttribute('ownerName', ''); + $templateVersion = $template->getAttribute('version', ''); + + $templateRootDirectory = $template->getAttribute('rootDirectory', ''); + $templateRootDirectory = \rtrim($templateRootDirectory, '/'); + $templateRootDirectory = \ltrim($templateRootDirectory, '.'); + $templateRootDirectory = \ltrim($templateRootDirectory, '/'); + + if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { + $stdout = ''; + $stderr = ''; + + // Clone template repo + $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template'; + $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); + $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to clone code repository: ' . $stderr); + } + + Console::execute('find ' . \escapeshellarg($tmpTemplateDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); + + // Ensure directories + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); + + $tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz'; + + $localDevice = new Local(); + + if (substr($tmpTemplateDirectory, -1) !== '/') { + $tmpTemplateDirectory .= '/'; + } + + $tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory)); + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + + $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); + $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); + + if (!$result) { + throw new \Exception("Unable to move file"); + } + + Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr); + + $directorySize = $deviceForFunctions->getFileSize($source); + $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize)); + } + } elseif ($isNewBuild && $isVcsEnabled) { + // VCS and VCS+Temaplte + $tmpDirectory = '/tmp/builds/' . $buildId . '/code'; + $rootDirectory = $function->getAttribute('providerRootDirectory', ''); + $rootDirectory = \rtrim($rootDirectory, '/'); + $rootDirectory = \ltrim($rootDirectory, '.'); + $rootDirectory = \ltrim($rootDirectory, '/'); + + $owner = $github->getOwnerName($providerInstallationId); + $repositoryName = $github->getRepositoryName($providerRepositoryId); + + $cloneOwner = $deployment->getAttribute('providerRepositoryOwner', $owner); + $cloneRepository = $deployment->getAttribute('providerRepositoryName', $repositoryName); + + $branchName = $deployment->getAttribute('providerBranch'); + $commitHash = $deployment->getAttribute('providerCommitHash', ''); + + $cloneVersion = $branchName; + $cloneType = GitHub::CLONE_TYPE_BRANCH; + if (!empty($commitHash)) { + $cloneVersion = $commitHash; + $cloneType = GitHub::CLONE_TYPE_COMMIT; + } + + $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory); + $stdout = ''; + $stderr = ''; + + Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr); + + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + + $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to clone code repository: ' . $stderr); + } + + // Local refactoring for function folder with spaces + if (str_contains($rootDirectory, ' ')) { + $rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory); + $from = $tmpDirectory . '/' . $rootDirectory; + $to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces; + $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to move function with spaces' . $stderr); + } + $rootDirectory = $rootDirectoryWithoutSpaces; + } + + + // Build from template + $templateRepositoryName = $template->getAttribute('repositoryName', ''); + $templateOwnerName = $template->getAttribute('ownerName', ''); + $templateVersion = $template->getAttribute('version', ''); + + $templateRootDirectory = $template->getAttribute('rootDirectory', ''); + $templateRootDirectory = \rtrim($templateRootDirectory, '/'); + $templateRootDirectory = \ltrim($templateRootDirectory, '.'); + $templateRootDirectory = \ltrim($templateRootDirectory, '/'); + + if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { + // Clone template repo + $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; + + $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); + $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to clone code repository: ' . $stderr); + } + + // Ensure directories + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); + Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); + + // Merge template into user repo + Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); + + // Commit and push + $exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to push code repository: ' . $stderr); + } + + $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr); + + if ($exit !== 0) { + throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); + } + + $providerCommitHash = \trim($stdout); + $authorUrl = "https://github.com/$cloneOwner"; + + $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); + $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); + $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); + $deployment->setAttribute('providerCommitMessage', "Create '" . $function->getAttribute('name', '') . "' function"); + $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); + + /** + * Send realtime Event + */ + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( + projectId: 'console', + payload: $build->getArrayCopy(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'] + ); + } + + $tmpPath = '/tmp/builds/' . $buildId; + $tmpPathFile = $tmpPath . '/code.tar.gz'; + $localDevice = new Local(); + + if (substr($tmpDirectory, -1) !== '/') { + $tmpDirectory .= '/'; + } + + $directorySize = $localDevice->getDirectorySize($tmpDirectory); + $functionsSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000'); + if ($directorySize > $functionsSizeLimit) { + throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.'); + } + + Console::execute('find ' . \escapeshellarg($tmpDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); + + $tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory); + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + + $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); + $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); + + if (!$result) { + throw new \Exception("Unable to move file"); + } + + Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr); + + $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); + + $directorySize = $deviceForFunctions->getFileSize($source); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize)); + + $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); + } + + /** Request the executor to build the code... */ + $build->setAttribute('status', 'building'); + $build = $dbForProject->updateDocument('builds', $buildId, $build); + + if ($isVcsEnabled) { + $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); + } + + /** Trigger Webhook */ + $deploymentModel = new Deployment(); + $deploymentUpdate = + $queueForEvents + ->setQueue(Event::WEBHOOK_QUEUE_NAME) + ->setClass(Event::WEBHOOK_CLASS_NAME) + ->setProject($project) + ->setEvent('functions.[functionId].deployments.[deploymentId].update') + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()) + ->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules()))); + + $deploymentUpdate->trigger(); + + /** Trigger Functions */ + $queueForFunctions + ->from($deploymentUpdate) + ->trigger(); + + /** Trigger Realtime */ + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + + Realtime::send( + projectId: 'console', + payload: $build->getArrayCopy(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'] + ); + + $vars = []; + + // Shared vars + foreach ($function->getAttribute('varsProject', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } + + // Function vars + foreach ($function->getAttribute('vars', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } + + $cpus = $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT; + $memory = max($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, 1024); // We have a minimum of 1024MB here because some runtimes can't compile with less memory than this. + + $jwtExpiry = (int)System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900); + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); + $apiKey = $jwtObj->encode([ + 'projectId' => $project->getId(), + 'scopes' => $function->getAttribute('scopes', []) + ]); + + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; + $hostname = System::getEnv('_APP_DOMAIN'); + $endpoint = $protocol . '://' . $hostname . "/v1"; + + // Appwrite vars + $vars = \array_merge($vars, [ + 'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint, + 'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey, + 'APPWRITE_FUNCTION_ID' => $function->getId(), + 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'), + 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(), + 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), + 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', + 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', + 'APPWRITE_FUNCTION_CPUS' => $cpus, + 'APPWRITE_FUNCTION_MEMORY' => $memory, + 'APPWRITE_VERSION' => APP_VERSION_STABLE, + 'APPWRITE_REGION' => $project->getAttribute('region'), + 'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''), + 'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''), + 'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''), + 'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''), + 'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''), + 'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''), + 'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''), + 'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''), + 'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''), + 'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''), + 'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''), + 'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''), + 'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''), + ]); + + $command = $deployment->getAttribute('commands', ''); + + $response = null; + $err = null; + + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + + $isCanceled = false; + + Co::join([ + Co\go(function () use ($executor, &$response, $project, $deployment, $source, $function, $runtime, $vars, $command, $cpus, $memory, &$err) { + try { + $version = $function->getAttribute('version', 'v2'); + $command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"'; + + $response = $executor->createRuntime( + deploymentId: $deployment->getId(), + projectId: $project->getId(), + source: $source, + image: $runtime['image'], + version: $version, + cpus: $cpus, + memory: $memory, + remove: true, + entrypoint: $deployment->getAttribute('entrypoint'), + destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}", + variables: $vars, + command: $command + ); + } catch (\Throwable $error) { + $err = $error; + } + }), + Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) { + try { + $executor->getLogs( + deploymentId: $deployment->getId(), + projectId: $project->getId(), + callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) { + if ($isCanceled) { + return; + } + + // If we have response or error from concurrent coroutine, we already have latest logs + if ($response === null && $err === null) { + $build = $dbForProject->getDocument('builds', $build->getId()); + + if ($build->isEmpty()) { + throw new \Exception('Build not found'); + } + + if ($build->getAttribute('status') === 'canceled') { + $isCanceled = true; + Console::info('Ignoring realtime logs because build has been canceled'); + return; + } + + $logs = \mb_substr($logs, 0, null, 'UTF-8'); // Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors + + $build = $build->setAttribute('logs', $build->getAttribute('logs', '') . $logs); + $build = $dbForProject->updateDocument('builds', $build->getId(), $build); + + /** + * Send realtime Event + */ + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( + projectId: 'console', + payload: $build->getArrayCopy(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'] + ); + } + } + ); + } catch (\Throwable $error) { + if (empty($err)) { + $err = $error; + } + } + }), + ]); + + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + + if ($err) { + throw $err; + } + + $endTime = DateTime::now(); + $durationEnd = \microtime(true); + + $buildSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_BUILD_SIZE_LIMIT', '2000000000'); + if ($response['size'] > $buildSizeLimit) { + throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.'); + } + + /** Update the build document */ + $build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime'])))); + $build->setAttribute('endTime', $endTime); + $build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart))); + $build->setAttribute('status', 'ready'); + $build->setAttribute('path', $response['path']); + $build->setAttribute('size', $response['size']); + $build->setAttribute('logs', $response['output']); + + $build = $dbForProject->updateDocument('builds', $buildId, $build); + + if ($isVcsEnabled) { + $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); + } + + Console::success("Build id: $buildId created"); + + /** Set auto deploy */ + if ($deployment->getAttribute('activate') === true) { + $function->setAttribute('deploymentInternalId', $deployment->getInternalId()); + $function->setAttribute('deployment', $deployment->getId()); + $function->setAttribute('live', true); + $function = $dbForProject->updateDocument('functions', $function->getId(), $function); + } + + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + + /** Update function schedule */ + + // Inform scheduler if function is still active + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + } catch (\Throwable $th) { + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + + $endTime = DateTime::now(); + $durationEnd = \microtime(true); + $build->setAttribute('endTime', $endTime); + $build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart))); + $build->setAttribute('status', 'failed'); + $build->setAttribute('logs', $th->getMessage()); + + $build = $dbForProject->updateDocument('builds', $buildId, $build); + + if ($isVcsEnabled) { + $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); + } + } finally { + /** + * Send realtime Event + */ + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( + projectId: 'console', + payload: $build->getArrayCopy(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'] + ); + + /** Trigger usage queue */ + if ($build->getAttribute('status') === 'ready') { + $queueForStatsUsage + ->addMetric(METRIC_BUILDS_SUCCESS, 1) // per project + ->addMetric(METRIC_BUILDS_COMPUTE_SUCCESS, (int)$build->getAttribute('duration', 0) * 1000) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS), 1) // per function + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS), (int)$build->getAttribute('duration', 0) * 1000); + } elseif ($build->getAttribute('status') === 'failed') { + $queueForStatsUsage + ->addMetric(METRIC_BUILDS_FAILED, 1) // per project + ->addMetric(METRIC_BUILDS_COMPUTE_FAILED, (int)$build->getAttribute('duration', 0) * 1000) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_FAILED), 1) // per function + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED), (int)$build->getAttribute('duration', 0) * 1000); + } + + $queueForStatsUsage + ->addMetric(METRIC_BUILDS, 1) // per project + ->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0)) + ->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000) + ->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1) // per function + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0)) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) + ->setProject($project) + ->trigger(); + } + } + + /** + * @param string $status + * @param GitHub $github + * @param string $providerCommitHash + * @param string $owner + * @param string $repositoryName + * @param Document $project + * @param Document $function + * @param string $deploymentId + * @param Database $dbForProject + * @param Database $dbForPlatform + * @return void + * @throws Structure + * @throws \Utopia\Database\Exception + * @throws Authorization + * @throws Conflict + * @throws Restricted + */ + protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForPlatform): void + { + if ($function->getAttribute('providerSilentMode', false) === true) { + return; + } + + $deployment = $dbForProject->getDocument('deployments', $deploymentId); + $commentId = $deployment->getAttribute('providerCommentId', ''); + + if (!empty($providerCommitHash)) { + $message = match ($status) { + 'ready' => 'Build succeeded.', + 'failed' => 'Build failed.', + 'processing' => 'Building...', + default => $status + }; + + $state = match ($status) { + 'ready' => 'success', + 'failed' => 'failure', + 'processing' => 'pending', + default => $status + }; + + $functionName = $function->getAttribute('name'); + $projectName = $project->getAttribute('name'); + + $name = "{$functionName} ({$projectName})"; + + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; + $hostname = System::getEnv('_APP_DOMAIN'); + $functionId = $function->getId(); + $projectId = $project->getId(); + $providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$projectId/functions/function-$functionId"; + + $github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name); + } + + if (!empty($commentId)) { + $retries = 0; + + while (true) { + $retries++; + + try { + $dbForPlatform->createDocument('vcsCommentLocks', new Document([ + '$id' => $commentId + ])); + break; + } catch (\Throwable $err) { + if ($retries >= 9) { + throw $err; + } + + \sleep(1); + } + } + + // Wrap in try/finally to ensure lock file gets deleted + try { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); + $comment->addBuild($project, $function, $status, $deployment->getId(), ['type' => 'logs']); + $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId); + } + } + } +} From fadb24f29541a8a43768bae800844385b92b76eb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 10 Mar 2025 00:21:49 +0100 Subject: [PATCH 003/385] revert change to build --- src/Appwrite/Platform/Workers/Builds.php | 829 ----------------------- 1 file changed, 829 deletions(-) delete mode 100644 src/Appwrite/Platform/Workers/Builds.php diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php deleted file mode 100644 index c9833adcfb..0000000000 --- a/src/Appwrite/Platform/Workers/Builds.php +++ /dev/null @@ -1,829 +0,0 @@ -desc('Builds worker') - ->inject('message') - ->inject('project') - ->inject('dbForPlatform') - ->inject('queueForEvents') - ->inject('queueForFunctions') - ->inject('queueForStatsUsage') - ->inject('cache') - ->inject('dbForProject') - ->inject('deviceForFunctions') - ->inject('isResourceBlocked') - ->inject('log') - ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) => - $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log)); - } - - /** - * @param Message $message - * @param Document $project - * @param Database $dbForPlatform - * @param Event $queueForEvents - * @param Func $queueForFunctions - * @param StatsUsage $queueForStatsUsage - * @param Cache $cache - * @param Database $dbForProject - * @param Device $deviceForFunctions - * @param Log $log - * @return void - * @throws \Utopia\Database\Exception - */ - public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void - { - $payload = $message->getPayload() ?? []; - - if (empty($payload)) { - throw new \Exception('Missing payload'); - } - - $type = $payload['type'] ?? ''; - $resource = new Document($payload['resource'] ?? []); - $deployment = new Document($payload['deployment'] ?? []); - $template = new Document($payload['template'] ?? []); - - $log->addTag('projectId', $project->getId()); - $log->addTag('type', $type); - - switch ($type) { - case BUILD_TYPE_DEPLOYMENT: - case BUILD_TYPE_RETRY: - Console::info('Creating build for deployment: ' . $deployment->getId()); - $github = new GitHub($cache); - $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log); - break; - - default: - throw new \Exception('Invalid build type'); - } - } - - /** - * @param Device $deviceForFunctions - * @param Func $queueForFunctions - * @param Event $queueForEvents - * @param StatsUsage $queueForStatsUsage - * @param Database $dbForPlatform - * @param Database $dbForProject - * @param GitHub $github - * @param Document $project - * @param Document $function - * @param Document $deployment - * @param Document $template - * @param Log $log - * @return void - * @throws \Utopia\Database\Exception - * @throws Exception - */ - protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void - { - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); - - $functionId = $function->getId(); - $log->addTag('functionId', $function->getId()); - - $function = $dbForProject->getDocument('functions', $functionId); - if ($function->isEmpty()) { - throw new \Exception('Function not found'); - } - - if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $functionId)) { - throw new \Exception('Function blocked'); - } - - $deploymentId = $deployment->getId(); - $log->addTag('deploymentId', $deploymentId); - - $deployment = $dbForProject->getDocument('deployments', $deploymentId); - if ($deployment->isEmpty()) { - throw new \Exception('Deployment not found'); - } - - if (empty($deployment->getAttribute('entrypoint', ''))) { - throw new \Exception('Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".'); - } - - $version = $function->getAttribute('version', 'v2'); - $spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)]; - $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); - $key = $function->getAttribute('runtime'); - $runtime = $runtimes[$key] ?? null; - if (\is_null($runtime)) { - throw new \Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); - } - - // Realtime preparation - $allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [ - 'functionId' => $function->getId(), - 'deploymentId' => $deployment->getId() - ]); - - $startTime = DateTime::now(); - $durationStart = \microtime(true); - $buildId = $deployment->getAttribute('buildId', ''); - $build = $dbForProject->getDocument('builds', $buildId); - $isNewBuild = empty($buildId); - if ($build->isEmpty()) { - $buildId = ID::unique(); - $build = $dbForProject->createDocument('builds', new Document([ - '$id' => $buildId, - '$permissions' => [], - 'startTime' => $startTime, - 'deploymentInternalId' => $deployment->getInternalId(), - 'deploymentId' => $deployment->getId(), - 'status' => 'processing', - 'path' => '', - 'runtime' => $function->getAttribute('runtime'), - 'source' => $deployment->getAttribute('path', ''), - 'sourceType' => strtolower($deviceForFunctions->getType()), - 'logs' => '', - 'endTime' => null, - 'duration' => 0, - 'size' => 0 - ])); - - $deployment->setAttribute('buildId', $build->getId()); - $deployment->setAttribute('buildInternalId', $build->getInternalId()); - $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); - } elseif ($build->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } else { - $build = $dbForProject->getDocument('builds', $buildId); - } - - $source = $deployment->getAttribute('path', ''); - $installationId = $deployment->getAttribute('installationId', ''); - $providerRepositoryId = $deployment->getAttribute('providerRepositoryId', ''); - $providerCommitHash = $deployment->getAttribute('providerCommitHash', ''); - $isVcsEnabled = !empty($providerRepositoryId); - $owner = ''; - $repositoryName = ''; - - if ($isVcsEnabled) { - $installation = $dbForPlatform->getDocument('installations', $installationId); - $providerInstallationId = $installation->getAttribute('providerInstallationId'); - $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); - $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); - - $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); - } - - try { - if ($isNewBuild && !$isVcsEnabled) { - // Non-VCS + Template - $templateRepositoryName = $template->getAttribute('repositoryName', ''); - $templateOwnerName = $template->getAttribute('ownerName', ''); - $templateVersion = $template->getAttribute('version', ''); - - $templateRootDirectory = $template->getAttribute('rootDirectory', ''); - $templateRootDirectory = \rtrim($templateRootDirectory, '/'); - $templateRootDirectory = \ltrim($templateRootDirectory, '.'); - $templateRootDirectory = \ltrim($templateRootDirectory, '/'); - - if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { - $stdout = ''; - $stderr = ''; - - // Clone template repo - $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template'; - $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); - } - - Console::execute('find ' . \escapeshellarg($tmpTemplateDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); - - // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); - - $tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz'; - - $localDevice = new Local(); - - if (substr($tmpTemplateDirectory, -1) !== '/') { - $tmpTemplateDirectory .= '/'; - } - - $tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory)); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax - - $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); - $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); - - if (!$result) { - throw new \Exception("Unable to move file"); - } - - Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr); - - $directorySize = $deviceForFunctions->getFileSize($source); - $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); - $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize)); - } - } elseif ($isNewBuild && $isVcsEnabled) { - // VCS and VCS+Temaplte - $tmpDirectory = '/tmp/builds/' . $buildId . '/code'; - $rootDirectory = $function->getAttribute('providerRootDirectory', ''); - $rootDirectory = \rtrim($rootDirectory, '/'); - $rootDirectory = \ltrim($rootDirectory, '.'); - $rootDirectory = \ltrim($rootDirectory, '/'); - - $owner = $github->getOwnerName($providerInstallationId); - $repositoryName = $github->getRepositoryName($providerRepositoryId); - - $cloneOwner = $deployment->getAttribute('providerRepositoryOwner', $owner); - $cloneRepository = $deployment->getAttribute('providerRepositoryName', $repositoryName); - - $branchName = $deployment->getAttribute('providerBranch'); - $commitHash = $deployment->getAttribute('providerCommitHash', ''); - - $cloneVersion = $branchName; - $cloneType = GitHub::CLONE_TYPE_BRANCH; - if (!empty($commitHash)) { - $cloneVersion = $commitHash; - $cloneType = GitHub::CLONE_TYPE_COMMIT; - } - - $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory); - $stdout = ''; - $stderr = ''; - - Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr); - - if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } - - $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); - } - - // Local refactoring for function folder with spaces - if (str_contains($rootDirectory, ' ')) { - $rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory); - $from = $tmpDirectory . '/' . $rootDirectory; - $to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces; - $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to move function with spaces' . $stderr); - } - $rootDirectory = $rootDirectoryWithoutSpaces; - } - - - // Build from template - $templateRepositoryName = $template->getAttribute('repositoryName', ''); - $templateOwnerName = $template->getAttribute('ownerName', ''); - $templateVersion = $template->getAttribute('version', ''); - - $templateRootDirectory = $template->getAttribute('rootDirectory', ''); - $templateRootDirectory = \rtrim($templateRootDirectory, '/'); - $templateRootDirectory = \ltrim($templateRootDirectory, '.'); - $templateRootDirectory = \ltrim($templateRootDirectory, '/'); - - if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { - // Clone template repo - $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; - - $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); - } - - // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); - Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); - - // Merge template into user repo - Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); - - // Commit and push - $exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to push code repository: ' . $stderr); - } - - $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); - } - - $providerCommitHash = \trim($stdout); - $authorUrl = "https://github.com/$cloneOwner"; - - $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); - $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); - $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); - $deployment->setAttribute('providerCommitMessage', "Create '" . $function->getAttribute('name', '') . "' function"); - $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); - $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); - - /** - * Send realtime Event - */ - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $build, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $build->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - } - - $tmpPath = '/tmp/builds/' . $buildId; - $tmpPathFile = $tmpPath . '/code.tar.gz'; - $localDevice = new Local(); - - if (substr($tmpDirectory, -1) !== '/') { - $tmpDirectory .= '/'; - } - - $directorySize = $localDevice->getDirectorySize($tmpDirectory); - $functionsSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000'); - if ($directorySize > $functionsSizeLimit) { - throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.'); - } - - Console::execute('find ' . \escapeshellarg($tmpDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); - - $tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax - - $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); - $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); - - if (!$result) { - throw new \Exception("Unable to move file"); - } - - Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr); - - $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); - - $directorySize = $deviceForFunctions->getFileSize($source); - $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize)); - - $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); - } - - /** Request the executor to build the code... */ - $build->setAttribute('status', 'building'); - $build = $dbForProject->updateDocument('builds', $buildId, $build); - - if ($isVcsEnabled) { - $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); - } - - /** Trigger Webhook */ - $deploymentModel = new Deployment(); - $deploymentUpdate = - $queueForEvents - ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setProject($project) - ->setEvent('functions.[functionId].deployments.[deploymentId].update') - ->setParam('functionId', $function->getId()) - ->setParam('deploymentId', $deployment->getId()) - ->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules()))); - - $deploymentUpdate->trigger(); - - /** Trigger Functions */ - $queueForFunctions - ->from($deploymentUpdate) - ->trigger(); - - /** Trigger Realtime */ - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $build, - project: $project - ); - - Realtime::send( - projectId: 'console', - payload: $build->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - - $vars = []; - - // Shared vars - foreach ($function->getAttribute('varsProject', []) as $var) { - $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); - } - - // Function vars - foreach ($function->getAttribute('vars', []) as $var) { - $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); - } - - $cpus = $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT; - $memory = max($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, 1024); // We have a minimum of 1024MB here because some runtimes can't compile with less memory than this. - - $jwtExpiry = (int)System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900); - $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); - $apiKey = $jwtObj->encode([ - 'projectId' => $project->getId(), - 'scopes' => $function->getAttribute('scopes', []) - ]); - - $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; - $hostname = System::getEnv('_APP_DOMAIN'); - $endpoint = $protocol . '://' . $hostname . "/v1"; - - // Appwrite vars - $vars = \array_merge($vars, [ - 'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint, - 'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey, - 'APPWRITE_FUNCTION_ID' => $function->getId(), - 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'), - 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(), - 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), - 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', - 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', - 'APPWRITE_FUNCTION_CPUS' => $cpus, - 'APPWRITE_FUNCTION_MEMORY' => $memory, - 'APPWRITE_VERSION' => APP_VERSION_STABLE, - 'APPWRITE_REGION' => $project->getAttribute('region'), - 'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''), - 'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''), - 'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''), - 'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''), - 'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''), - 'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''), - 'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''), - 'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''), - 'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''), - 'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''), - 'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''), - 'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''), - 'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''), - ]); - - $command = $deployment->getAttribute('commands', ''); - - $response = null; - $err = null; - - if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } - - $isCanceled = false; - - Co::join([ - Co\go(function () use ($executor, &$response, $project, $deployment, $source, $function, $runtime, $vars, $command, $cpus, $memory, &$err) { - try { - $version = $function->getAttribute('version', 'v2'); - $command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"'; - - $response = $executor->createRuntime( - deploymentId: $deployment->getId(), - projectId: $project->getId(), - source: $source, - image: $runtime['image'], - version: $version, - cpus: $cpus, - memory: $memory, - remove: true, - entrypoint: $deployment->getAttribute('entrypoint'), - destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}", - variables: $vars, - command: $command - ); - } catch (\Throwable $error) { - $err = $error; - } - }), - Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) { - try { - $executor->getLogs( - deploymentId: $deployment->getId(), - projectId: $project->getId(), - callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) { - if ($isCanceled) { - return; - } - - // If we have response or error from concurrent coroutine, we already have latest logs - if ($response === null && $err === null) { - $build = $dbForProject->getDocument('builds', $build->getId()); - - if ($build->isEmpty()) { - throw new \Exception('Build not found'); - } - - if ($build->getAttribute('status') === 'canceled') { - $isCanceled = true; - Console::info('Ignoring realtime logs because build has been canceled'); - return; - } - - $logs = \mb_substr($logs, 0, null, 'UTF-8'); // Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors - - $build = $build->setAttribute('logs', $build->getAttribute('logs', '') . $logs); - $build = $dbForProject->updateDocument('builds', $build->getId(), $build); - - /** - * Send realtime Event - */ - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $build, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $build->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - } - } - ); - } catch (\Throwable $error) { - if (empty($err)) { - $err = $error; - } - } - }), - ]); - - if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } - - if ($err) { - throw $err; - } - - $endTime = DateTime::now(); - $durationEnd = \microtime(true); - - $buildSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_BUILD_SIZE_LIMIT', '2000000000'); - if ($response['size'] > $buildSizeLimit) { - throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.'); - } - - /** Update the build document */ - $build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime'])))); - $build->setAttribute('endTime', $endTime); - $build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart))); - $build->setAttribute('status', 'ready'); - $build->setAttribute('path', $response['path']); - $build->setAttribute('size', $response['size']); - $build->setAttribute('logs', $response['output']); - - $build = $dbForProject->updateDocument('builds', $buildId, $build); - - if ($isVcsEnabled) { - $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); - } - - Console::success("Build id: $buildId created"); - - /** Set auto deploy */ - if ($deployment->getAttribute('activate') === true) { - $function->setAttribute('deploymentInternalId', $deployment->getInternalId()); - $function->setAttribute('deployment', $deployment->getId()); - $function->setAttribute('live', true); - $function = $dbForProject->updateDocument('functions', $function->getId(), $function); - } - - if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } - - /** Update function schedule */ - - // Inform scheduler if function is still active - $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('schedule', $function->getAttribute('schedule')) - ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); - } catch (\Throwable $th) { - if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { - Console::info('Build has been canceled'); - return; - } - - $endTime = DateTime::now(); - $durationEnd = \microtime(true); - $build->setAttribute('endTime', $endTime); - $build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart))); - $build->setAttribute('status', 'failed'); - $build->setAttribute('logs', $th->getMessage()); - - $build = $dbForProject->updateDocument('builds', $buildId, $build); - - if ($isVcsEnabled) { - $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); - } - } finally { - /** - * Send realtime Event - */ - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $build, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $build->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - - /** Trigger usage queue */ - if ($build->getAttribute('status') === 'ready') { - $queueForStatsUsage - ->addMetric(METRIC_BUILDS_SUCCESS, 1) // per project - ->addMetric(METRIC_BUILDS_COMPUTE_SUCCESS, (int)$build->getAttribute('duration', 0) * 1000) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS), 1) // per function - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS), (int)$build->getAttribute('duration', 0) * 1000); - } elseif ($build->getAttribute('status') === 'failed') { - $queueForStatsUsage - ->addMetric(METRIC_BUILDS_FAILED, 1) // per project - ->addMetric(METRIC_BUILDS_COMPUTE_FAILED, (int)$build->getAttribute('duration', 0) * 1000) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_FAILED), 1) // per function - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED), (int)$build->getAttribute('duration', 0) * 1000); - } - - $queueForStatsUsage - ->addMetric(METRIC_BUILDS, 1) // per project - ->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0)) - ->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000) - ->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1) // per function - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0)) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) - ->setProject($project) - ->trigger(); - } - } - - /** - * @param string $status - * @param GitHub $github - * @param string $providerCommitHash - * @param string $owner - * @param string $repositoryName - * @param Document $project - * @param Document $function - * @param string $deploymentId - * @param Database $dbForProject - * @param Database $dbForPlatform - * @return void - * @throws Structure - * @throws \Utopia\Database\Exception - * @throws Authorization - * @throws Conflict - * @throws Restricted - */ - protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForPlatform): void - { - if ($function->getAttribute('providerSilentMode', false) === true) { - return; - } - - $deployment = $dbForProject->getDocument('deployments', $deploymentId); - $commentId = $deployment->getAttribute('providerCommentId', ''); - - if (!empty($providerCommitHash)) { - $message = match ($status) { - 'ready' => 'Build succeeded.', - 'failed' => 'Build failed.', - 'processing' => 'Building...', - default => $status - }; - - $state = match ($status) { - 'ready' => 'success', - 'failed' => 'failure', - 'processing' => 'pending', - default => $status - }; - - $functionName = $function->getAttribute('name'); - $projectName = $project->getAttribute('name'); - - $name = "{$functionName} ({$projectName})"; - - $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; - $hostname = System::getEnv('_APP_DOMAIN'); - $functionId = $function->getId(); - $projectId = $project->getId(); - $providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$projectId/functions/function-$functionId"; - - $github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name); - } - - if (!empty($commentId)) { - $retries = 0; - - while (true) { - $retries++; - - try { - $dbForPlatform->createDocument('vcsCommentLocks', new Document([ - '$id' => $commentId - ])); - break; - } catch (\Throwable $err) { - if ($retries >= 9) { - throw $err; - } - - \sleep(1); - } - } - - // Wrap in try/finally to ensure lock file gets deleted - try { - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); - $comment->addBuild($project, $function, $status, $deployment->getId(), ['type' => 'logs']); - $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); - } finally { - $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId); - } - } - } -} From a058925252d79e302d3ea86701df1e7a35cb55e3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 10 Mar 2025 00:23:22 +0100 Subject: [PATCH 004/385] Code formatting --- app/controllers/api/users.php | 19 +++++++++---------- app/controllers/shared/api.php | 12 ++++++------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4a0c6013ed..31b6e61bad 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -29,6 +29,15 @@ use MaxMind\Db\Reader; use Utopia\App; use Utopia\Audit\Audit; use Utopia\Auth\Hash; +use Utopia\Auth\Hashes\Argon2; +use Utopia\Auth\Hashes\Bcrypt; +use Utopia\Auth\Hashes\MD5; +use Utopia\Auth\Hashes\PHPass; +use Utopia\Auth\Hashes\Plaintext; +use Utopia\Auth\Hashes\Scrypt; +use Utopia\Auth\Hashes\ScryptModified; +use Utopia\Auth\Hashes\Sha; +use Utopia\Auth\Proofs\Password as ProofsPassword; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -54,15 +63,6 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; -use Utopia\Auth\Hashes\Argon2; -use Utopia\Auth\Hashes\Bcrypt; -use Utopia\Auth\Hashes\MD5; -use Utopia\Auth\Hashes\PHPass; -use Utopia\Auth\Hashes\Scrypt; -use Utopia\Auth\Hashes\ScryptModified; -use Utopia\Auth\Hashes\Sha; -use Utopia\Auth\Hashes\Plaintext; -use Utopia\Auth\Proofs\Password as ProofsPassword; /** TODO: Remove function when we move to using utopia/platform */ function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document @@ -2515,4 +2515,3 @@ App::get('/v1/users/usage') 'sessions' => $usage[$metrics[1]]['data'], ]), Response::MODEL_USAGE_USERS); }); - diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f87ddf9730..bc9ce80b0d 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -202,18 +202,18 @@ App::init() /** * Handle user authentication and session validation. - * + * * This function follows a series of steps to determine the appropriate user session * based on cookies, headers, and JWT tokens. - * + * * Process: - * + * * Project & Role Validation: * 1. Check if the project is empty. If so, throw an exception. * 2. Get the roles configuration. * 3. Determine the role for the user based on the user document. * 4. Get the scopes for the role. - * + * * API Key Authentication: * 5. If there is an API key: * - Verify no user session exists simultaneously @@ -237,7 +237,7 @@ App::init() * 12. Validate MFA requirements: * - Check if MFA is enabled * - Verify email status - * - Verify phone status + * - Verify phone status * - Verify authenticator status * 13. Handle Multi-Factor Authentication: * - Check remaining required factors @@ -255,7 +255,7 @@ App::init() // Step 3: Determine role for user // TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token. - + $role = $user->isEmpty() ? Role::guests()->toString() : Role::users()->toString(); From d1318cf0161a25275a9dd69de3a3d8b35d68e96c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 10 Mar 2025 02:19:57 +0100 Subject: [PATCH 005/385] Updated dependencies --- composer.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index ce424076c3..6adef9098a 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": "58ad2e1375ec47d944b96864d4aae63f", + "content-hash": "5820d9145556499015ac03aef8a7fb39", "packages": [ { "name": "adhocore/jwt", @@ -5990,6 +5990,65 @@ ], "time": "2025-01-26T19:54:45+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.8.11", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "46e223dd68a620da18855c23046ddb00940b4014" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", + "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-10-24T15:45:13+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.32", From c08355df8dd311cb2fd66388688e4d7bec8d1524 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 10:34:45 +0100 Subject: [PATCH 006/385] Updated composer --- composer.lock | 110 +++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/composer.lock b/composer.lock index b8b61754ec..94a4ec905b 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": "6e6444246ba207b6bf88c20340835fb5", + "content-hash": "a7592b2898066e8fe4eecf4fe94db6ed", "packages": [ { "name": "adhocore/jwt", @@ -709,16 +709,16 @@ }, { "name": "google/protobuf", - "version": "v4.30.0", + "version": "v4.30.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6" + "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/e1d66682f6836aa87820400f0aa07d9eb566feb6", - "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/f29ba8a30dfd940efb3a8a75dc44446539101f24", + "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24", "shasum": "" }, "require": { @@ -747,9 +747,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.1" }, - "time": "2025-03-04T22:54:49+00:00" + "time": "2025-03-13T21:08:17+00:00" }, { "name": "jean85/pretty-package-versions", @@ -3364,16 +3364,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.51.0", + "version": "0.52.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "661687b03277f1d202a0e8cf9da6e58c97da2b5e" + "reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/661687b03277f1d202a0e8cf9da6e58c97da2b5e", - "reference": "661687b03277f1d202a0e8cf9da6e58c97da2b5e", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/a0d6421e7e5baa3ac02755496dca9fdeaa814b93", + "reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93", "shasum": "" }, "require": { @@ -3381,7 +3381,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "0.60.*" + "utopia-php/database": "0.*.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3409,9 +3409,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.51.0" + "source": "https://github.com/utopia-php/abuse/tree/0.52.0" }, - "time": "2025-02-17T11:10:18+00:00" + "time": "2025-03-06T03:48:29+00:00" }, { "name": "utopia-php/analytics", @@ -3461,21 +3461,21 @@ }, { "name": "utopia-php/audit", - "version": "0.54.0", + "version": "0.55.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975" + "reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975", - "reference": "1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518", + "reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.60.*" + "utopia-php/database": "0.*.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3502,9 +3502,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.54.0" + "source": "https://github.com/utopia-php/audit/tree/0.55.0" }, - "time": "2025-02-25T07:21:07+00:00" + "time": "2025-03-06T03:47:47+00:00" }, { "name": "utopia-php/auth", @@ -3760,16 +3760,16 @@ }, { "name": "utopia-php/database", - "version": "0.60.6", + "version": "0.61.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f3c9aa964b39c6205069f038a26e709a15541406" + "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f3c9aa964b39c6205069f038a26e709a15541406", - "reference": "f3c9aa964b39c6205069f038a26e709a15541406", + "url": "https://api.github.com/repos/utopia-php/database/zipball/349fbdf4bc088f7775c7dfb8b80239a617a88436", + "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436", "shasum": "" }, "require": { @@ -3810,9 +3810,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.60.6" + "source": "https://github.com/utopia-php/database/tree/0.61.2" }, - "time": "2025-03-05T01:23:14+00:00" + "time": "2025-03-15T11:47:42+00:00" }, { "name": "utopia-php/detector", @@ -4259,16 +4259,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.20", + "version": "0.6.22", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba" + "reference": "a0269746bd318ff0993f5aa008675b971689d5b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba", - "reference": "8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/a0269746bd318ff0993f5aa008675b971689d5b5", + "reference": "a0269746bd318ff0993f5aa008675b971689d5b5", "shasum": "" }, "require": { @@ -4276,7 +4276,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.60.*", + "utopia-php/database": "0.61.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4309,9 +4309,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.20" + "source": "https://github.com/utopia-php/migration/tree/0.6.22" }, - "time": "2025-02-17T11:02:15+00:00" + "time": "2025-03-13T07:35:55+00:00" }, { "name": "utopia-php/mongo", @@ -4425,16 +4425,16 @@ }, { "name": "utopia-php/platform", - "version": "0.7.3", + "version": "0.7.4", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25" + "reference": "a5b93d8177702ec458c3af9137663133c012b71b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/463c2d817c893d7dbb678c2eac7a8291f2710e25", - "reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/a5b93d8177702ec458c3af9137663133c012b71b", + "reference": "a5b93d8177702ec458c3af9137663133c012b71b", "shasum": "" }, "require": { @@ -4443,7 +4443,7 @@ "php": ">=8.0", "utopia-php/cli": "0.15.*", "utopia-php/framework": "0.33.*", - "utopia-php/queue": "0.8.*" + "utopia-php/queue": "0.9.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -4469,9 +4469,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.7.3" + "source": "https://github.com/utopia-php/platform/tree/0.7.4" }, - "time": "2025-02-04T15:09:00+00:00" + "time": "2025-03-13T13:00:12+00:00" }, { "name": "utopia-php/pools", @@ -4579,16 +4579,16 @@ }, { "name": "utopia-php/queue", - "version": "0.8.6", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "b713b997285c29d120bbcbe3d6e93762d850f87c" + "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/b713b997285c29d120bbcbe3d6e93762d850f87c", - "reference": "b713b997285c29d120bbcbe3d6e93762d850f87c", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/077075f1d57afa430f76c35ed3bf4616e0eee8e7", + "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7", "shasum": "" }, "require": { @@ -4638,9 +4638,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.8.6" + "source": "https://github.com/utopia-php/queue/tree/0.9.0" }, - "time": "2025-02-10T03:35:00+00:00" + "time": "2025-03-13T12:22:41+00:00" }, { "name": "utopia-php/registry", @@ -5417,16 +5417,16 @@ }, { "name": "laravel/pint", - "version": "v1.21.1", + "version": "v1.21.2", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "c44bffbb2334e90fba560933c45948fa4a3f3e86" + "reference": "370772e7d9e9da087678a0edf2b11b6960e40558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/c44bffbb2334e90fba560933c45948fa4a3f3e86", - "reference": "c44bffbb2334e90fba560933c45948fa4a3f3e86", + "url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558", + "reference": "370772e7d9e9da087678a0edf2b11b6960e40558", "shasum": "" }, "require": { @@ -5437,9 +5437,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.70.2", - "illuminate/view": "^11.44.1", - "larastan/larastan": "^3.1.0", + "friendsofphp/php-cs-fixer": "^3.72.0", + "illuminate/view": "^11.44.2", + "larastan/larastan": "^3.2.0", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3", @@ -5479,7 +5479,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-03-11T03:22:21+00:00" + "time": "2025-03-14T22:31:42+00:00" }, { "name": "matthiasmullie/minify", From 706ce4b3b6ad5d63f3c80bc4e6c4582f7c6c560a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 18:30:48 +0100 Subject: [PATCH 007/385] WIP - cleaning up auth managment --- app/controllers/api/account.php | 79 +++++++++++++++++++++++---------- app/controllers/api/teams.php | 19 +++++--- app/controllers/api/users.php | 11 ++++- app/init.php | 44 ++++++++++-------- composer.lock | 8 ++-- tests/unit/Auth/AuthTest.php | 15 ++++++- 6 files changed, 120 insertions(+), 56 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 20f64496ac..0778bb32c1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -38,6 +38,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; +use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -154,7 +155,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc }; -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -255,16 +256,21 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setParam('userId', $user->getId()) ->setParam('sessionId', $session->getId()); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $sessionSecret) + ->encode(); + if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $sessionSecret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $protocol = $request->getProtocol(); $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -273,7 +279,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setAttribute('current', true) ->setAttribute('countryName', $countryName) ->setAttribute('expire', $expire) - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? Auth::encodeSession($user->getId(), $sessionSecret) : '') + ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') ; $response->dynamic($session, Response::MODEL_SESSION); @@ -881,7 +887,8 @@ App::post('/v1/account/sessions/email') ->inject('queueForEvents') ->inject('queueForMails') ->inject('hooks') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) { + ->inject('store') + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -947,17 +954,20 @@ App::post('/v1/account/sessions/email') Permission::delete(Role::user($user->getId())), ])); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -966,7 +976,7 @@ App::post('/v1/account/sessions/email') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? Auth::encodeSession($user->getId(), $secret) : '') + ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') ; $queueForEvents @@ -1017,7 +1027,8 @@ App::post('/v1/account/sessions/anonymous') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) { + ->inject('store') + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store) { $protocol = $request->getProtocol(); $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ -1106,15 +1117,20 @@ App::post('/v1/account/sessions/anonymous') ->setParam('sessionId', $session->getId()) ; + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1123,7 +1139,7 @@ App::post('/v1/account/sessions/anonymous') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? Auth::encodeSession($user->getId(), $secret) : '') + ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') ; $response->dynamic($session, Response::MODEL_SESSION); @@ -1163,6 +1179,7 @@ App::post('/v1/account/sessions/token') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('store') ->action($createSession); App::get('/v1/account/sessions/oauth2/:provider') @@ -1327,7 +1344,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) { + ->inject('store') + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store) use ($oauthDefaultSuccess) { $protocol = $request->getProtocol(); $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; @@ -1711,8 +1729,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $session->setAttribute('expire', $expire); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } $queueForEvents @@ -1726,12 +1749,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $query['project'] = $project->getId(); $query['domain'] = Config::getParam('cookieDomain'); $query['key'] = Auth::$cookieName; - $query['secret'] = Auth::encodeSession($user->getId(), $secret); + $query['secret'] = $encoded; } $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } if (isset($sessionUpgrade) && $sessionUpgrade) { @@ -2358,6 +2381,7 @@ App::put('/v1/account/sessions/magic-url') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('store') ->action($createSession); App::put('/v1/account/sessions/phone') @@ -2395,6 +2419,7 @@ App::put('/v1/account/sessions/phone') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('store') ->action($createSession); App::post('/v1/account/tokens/phone') @@ -2434,7 +2459,8 @@ App::post('/v1/account/tokens/phone') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { + ->inject('store') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -2600,8 +2626,13 @@ App::post('/v1/account/tokens/phone') $queueForEvents ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + // Hide secret for clients - $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? Auth::encodeSession($user->getId(), $secret) : ''); + $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : ''); $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index b45c9fd3b9..60d6960445 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -27,6 +27,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit; +use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -1126,7 +1127,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('project') ->inject('geodb') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) { + ->inject('store') + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1206,13 +1208,18 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Authorization::setRole(Role::user($userId)->toString()); if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + + $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } $response ->addCookie( - name: Auth::$cookieName . '_legacy', - value: Auth::encodeSession($user->getId(), $secret), + name: $store->getKey() . '_legacy', + value: $encoded, expire: (new \DateTime($expire))->getTimestamp(), path: '/', domain: Config::getParam('cookieDomain'), @@ -1220,8 +1227,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') httponly: true ) ->addCookie( - name: Auth::$cookieName, - value: Auth::encodeSession($user->getId(), $secret), + name: $store->getKey(), + value: $encoded, expire: (new \DateTime($expire))->getTimestamp(), path: '/', domain: Config::getParam('cookieDomain'), diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index f5cefd365b..e1753fd9b4 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -38,6 +38,7 @@ use Utopia\Auth\Hashes\Scrypt; use Utopia\Auth\Hashes\ScryptModified; use Utopia\Auth\Hashes\Sha; use Utopia\Auth\Proofs\Password as ProofsPassword; +use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -2014,7 +2015,8 @@ App::post('/v1/users/:userId/sessions') ->inject('locale') ->inject('geodb') ->inject('queueForEvents') - ->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) { + ->inject('store') + ->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -2057,8 +2059,13 @@ App::post('/v1/users/:userId/sessions') $dbForProject->purgeCachedDocument('users', $user->getId()); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + $session - ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) + ->setAttribute('secret', $encoded) ->setAttribute('countryName', $countryName); $queueForEvents diff --git a/app/init.php b/app/init.php index 8219517834..123edd3915 100644 --- a/app/init.php +++ b/app/init.php @@ -51,6 +51,7 @@ use PHPMailer\PHPMailer\PHPMailer; use Swoole\Database\PDOProxy; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Store; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; @@ -1286,13 +1287,14 @@ App::setResource('clients', function ($request, $console, $project) { return \array_unique($clients); }, ['request', 'console', 'project']); -App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) { +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, Store $store) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Database $dbForPlatform */ /** @var string $mode */ + /** @var Utopia\Auth\Store $store */ /** * Handles user authentication and session validation. @@ -1315,62 +1317,64 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons Authorization::setDefaultStatus(true); - Auth::setCookieName('a_session_' . $project->getId()); + $store->setKey('a_session_' . $project->getId()); if (APP_MODE_ADMIN === $mode) { - Auth::setCookieName('a_session_' . $console->getId()); + $store->setKey('a_session_' . $console->getId()); } - $session = Auth::decodeSession( + $store->decode( $request->getCookie( - Auth::$cookieName, // Get sessions - $request->getCookie(Auth::$cookieName . '_legacy', '') + $store->getKey(), // Get sessions + $request->getCookie($store->getKey() . '_legacy', '') ) ); + var_dump($store); + // Get session from header for SSR clients - if (empty($session['id']) && empty($session['secret'])) { + if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { $sessionHeader = $request->getHeader('x-appwrite-session', ''); if (!empty($sessionHeader)) { - $session = Auth::decodeSession($sessionHeader); + $store->decode($sessionHeader); } } // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies - if ($response) { + if ($response) { // if in http context - add debug header $response->addHeader('X-Debug-Fallback', 'false'); } - if (empty($session['id']) && empty($session['secret'])) { + if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { if ($response) { $response->addHeader('X-Debug-Fallback', 'true'); } $fallback = $request->getHeader('x-fallback-cookies', ''); $fallback = \json_decode($fallback, true); - $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); + $store->decode(((isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); } - Auth::$unique = $session['id'] ?? ''; - Auth::$secret = $session['secret'] ?? ''; + // Auth::$unique = $session['id'] ?? ''; + // Auth::$secret = $session['secret'] ?? ''; if (APP_MODE_ADMIN !== $mode) { if ($project->isEmpty()) { $user = new Document([]); } else { if ($project->getId() === 'console') { - $user = $dbForPlatform->getDocument('users', Auth::$unique); + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); } else { - $user = $dbForProject->getDocument('users', Auth::$unique); + $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); } } } else { - $user = $dbForPlatform->getDocument('users', Auth::$unique); + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); } if ( $user->isEmpty() // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) + || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')) ) { // Validate user has valid login token $user = new Document([]); } @@ -1411,7 +1415,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $dbForPlatform->setMetadata('user', $user->getId()); return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']); +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store']); App::setResource('project', function ($dbForPlatform, $request, $console) { /** @var Appwrite\Utopia\Request $request */ @@ -1976,3 +1980,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); + +App::setResource('store', function () { + return new Store(); +}); diff --git a/composer.lock b/composer.lock index 94a4ec905b..1997076f5e 100644 --- a/composer.lock +++ b/composer.lock @@ -3512,12 +3512,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "b063a2317c48cc6f3dba1eab0298641b19accdcd" + "reference": "ada77726740eb7180a4cee762cdf0c1beb0dc3a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/b063a2317c48cc6f3dba1eab0298641b19accdcd", - "reference": "b063a2317c48cc6f3dba1eab0298641b19accdcd", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/ada77726740eb7180a4cee762cdf0c1beb0dc3a3", + "reference": "ada77726740eb7180a4cee762cdf0c1beb0dc3a3", "shasum": "" }, "require": { @@ -3559,7 +3559,7 @@ "issues": "https://github.com/utopia-php/auth/issues", "source": "https://github.com/utopia-php/auth/tree/dev" }, - "time": "2025-03-09T22:50:59+00:00" + "time": "2025-03-16T16:32:38+00:00" }, { "name": "utopia-php/cache", diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 705da42879..147ad17977 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; use PHPUnit\Framework\TestCase; +use Utopia\Auth\Store; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -36,8 +37,18 @@ class AuthTest extends TestCase $secret = 'secret'; $session = 'eyJpZCI6ImlkIiwic2VjcmV0Ijoic2VjcmV0In0='; - $this->assertEquals(Auth::encodeSession($id, $secret), $session); - $this->assertEquals(Auth::decodeSession($session), ['id' => $id, 'secret' => $secret]); + $store = new Store(); + + $encoded = $store + ->setProperty('id', $id) + ->setProperty('secret', $secret) + ->encode(); + + $decoded = $store->decode($encoded); + + $this->assertEquals($encoded, $session); + $this->assertEquals($decoded->getProperty('id'), $id); + $this->assertEquals($decoded->getProperty('secret'), $secret); } public function testHash(): void From cc724218b00459aeccf26e44449dabf0c36348a3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 18:32:46 +0100 Subject: [PATCH 008/385] fixed formatting --- tests/unit/Auth/AuthTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 147ad17977..68865306ae 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -45,7 +45,7 @@ class AuthTest extends TestCase ->encode(); $decoded = $store->decode($encoded); - + $this->assertEquals($encoded, $session); $this->assertEquals($decoded->getProperty('id'), $id); $this->assertEquals($decoded->getProperty('secret'), $secret); From a5e57a9a6715d272f572752c36c2a52c9272bdd1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 18:54:55 +0100 Subject: [PATCH 009/385] Work in progress, moving to instance based auth --- app/controllers/api/account.php | 53 ++++++++-------- app/init.php | 11 +--- app/realtime.php | 10 +-- src/Appwrite/Auth/Auth.php | 63 ------------------- .../Functions/Http/Executions/Create.php | 7 ++- tests/unit/Auth/AuthTest.php | 29 --------- 6 files changed, 41 insertions(+), 132 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 0778bb32c1..3ac17e953e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -533,15 +533,15 @@ App::get('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('locale') - ->inject('project') - ->action(function (Response $response, Document $user, Locale $locale, Document $project) { + ->inject('store') + ->action(function (Response $response, Document $user, Locale $locale, Store $store) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, Auth::$secret); + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', '')); foreach ($sessions as $key => $session) {/** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -587,7 +587,8 @@ App::delete('/v1/account/sessions') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes) { + ->inject('store') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store) { $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); @@ -603,13 +604,13 @@ App::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { + if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { $session->setAttribute('current', true); // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); // Use current session for events. $queueForEvents @@ -652,8 +653,8 @@ App::get('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('locale') - ->inject('project') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) { + ->inject('store') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Store $store) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ -661,7 +662,7 @@ App::get('/v1/account/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ @@ -669,7 +670,7 @@ App::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret))) + ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', '')))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '') ; @@ -711,12 +712,12 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('project') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Document $project) { + ->inject('store') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -735,7 +736,7 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); @@ -745,8 +746,8 @@ App::delete('/v1/account/sessions/:sessionId') } $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } $dbForProject->purgeCachedDocument('users', $user->getId()); @@ -796,10 +797,11 @@ App::patch('/v1/account/sessions/:sessionId') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) { + ->inject('store') + ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store) { $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -1482,7 +1484,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, Auth::$secret); + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', '')); if ($current) { // Delete current session of new one. $currentDocument = $dbForProject->getDocument('sessions', $current); @@ -2662,15 +2664,15 @@ App::post('/v1/account/jwts') ->label('abuse-key', 'url:{url},userId:{userId}') ->inject('response') ->inject('user') - ->inject('dbForProject') - ->action(function (Response $response, Document $user, Database $dbForProject) { + ->inject('store') + ->action(function (Response $response, Document $user, Store $store) { $sessions = $user->getAttribute('sessions', []); $current = new Document(); foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too $current = $session; } } @@ -4650,7 +4652,8 @@ App::post('/v1/account/targets/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { + ->inject('store') + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Store $store) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); @@ -4666,7 +4669,7 @@ App::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')); $session = $dbForProject->getDocument('sessions', $sessionId); try { diff --git a/app/init.php b/app/init.php index 123edd3915..e54b7b3cc8 100644 --- a/app/init.php +++ b/app/init.php @@ -1330,8 +1330,6 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons ) ); - var_dump($store); - // Get session from header for SSR clients if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { $sessionHeader = $request->getHeader('x-appwrite-session', ''); @@ -1355,9 +1353,6 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $store->decode(((isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); } - // Auth::$unique = $session['id'] ?? ''; - // Auth::$secret = $session['secret'] ?? ''; - if (APP_MODE_ADMIN !== $mode) { if ($project->isEmpty()) { $user = new Document([]); @@ -1433,13 +1428,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) { return $project; }, ['dbForPlatform', 'request', 'console']); -App::setResource('session', function (Document $user) { +App::setResource('session', function (Document $user, Store $store) { if ($user->isEmpty()) { return; } $sessions = $user->getAttribute('sessions', []); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')); if (!$sessionId) { return; @@ -1452,7 +1447,7 @@ App::setResource('session', function (Document $user) { } return; -}, ['user']); +}, ['user', 'store']); App::setResource('console', function () { return new Document(Config::getParam('console')); diff --git a/app/realtime.php b/app/realtime.php index 86f9c85fdd..d65a2559b5 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -15,6 +15,7 @@ use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Store; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; use Utopia\CLI\Console; @@ -649,15 +650,14 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.'); } - $session = Auth::decodeSession($message['data']['session']); - Auth::$unique = $session['id'] ?? ''; - Auth::$secret = $session['secret'] ?? ''; + $store = new Store(); + $store->decode($message['data']['session']); - $user = $database->getDocument('users', Auth::$unique); + $user = $database->getDocument('users', $store->getProperty('id', '')); if ( empty($user->getId()) // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')) // Validate user has valid login token ) { // cookie not valid throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 9af5045fa4..ee1233fc10 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -108,48 +108,6 @@ class Auth */ public static $cookieNamePreview = 'a_jwt_console'; - /** - * User Unique ID. - * - * @var string - */ - public static $unique = ''; - - /** - * User Secret Key. - * - * @var string - */ - public static $secret = ''; - - /** - * Set Cookie Name. - * - * @param $string - * - * @return string - */ - public static function setCookieName($string) - { - return self::$cookieName = $string; - } - - /** - * Encode Session. - * - * @param string $id - * @param string $secret - * - * @return string - */ - public static function encodeSession($id, $secret) - { - return \base64_encode(\json_encode([ - 'id' => $id, - 'secret' => $secret, - ])); - } - /** * Token type to session provider mapping. */ @@ -171,27 +129,6 @@ class Auth } } - /** - * Decode Session. - * - * @param string $session - * - * @return array - * - * @throws \Exception - */ - public static function decodeSession($session) - { - $session = \json_decode(\base64_decode($session), true); - $default = ['id' => null, 'secret' => '']; - - if (!\is_array($session)) { - return $default; - } - - return \array_merge($default, $session); - } - /** * Encode. * diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 2e47c04276..3a7030742e 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -19,6 +19,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Executor\Executor; use MaxMind\Db\Reader; +use Utopia\Auth\Store; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -93,6 +94,7 @@ class Create extends Base ->inject('queueForStatsUsage') ->inject('queueForFunctions') ->inject('geodb') + ->inject('store') ->callback([$this, 'action']); } @@ -113,7 +115,8 @@ class Create extends Base Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, - Reader $geodb + Reader $geodb, + Store $store ) { $async = \strval($async) === 'true' || \strval($async) === '1'; @@ -190,7 +193,7 @@ class Create extends Base foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too $current = $session; } } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 68865306ae..2b19439da6 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -4,7 +4,6 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; use PHPUnit\Framework\TestCase; -use Utopia\Auth\Store; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -23,34 +22,6 @@ class AuthTest extends TestCase Authorization::setRole(Role::any()->toString()); } - public function testCookieName(): void - { - $name = 'cookie-name'; - - $this->assertEquals(Auth::setCookieName($name), $name); - $this->assertEquals(Auth::$cookieName, $name); - } - - public function testEncodeDecodeSession(): void - { - $id = 'id'; - $secret = 'secret'; - $session = 'eyJpZCI6ImlkIiwic2VjcmV0Ijoic2VjcmV0In0='; - - $store = new Store(); - - $encoded = $store - ->setProperty('id', $id) - ->setProperty('secret', $secret) - ->encode(); - - $decoded = $store->decode($encoded); - - $this->assertEquals($encoded, $session); - $this->assertEquals($decoded->getProperty('id'), $id); - $this->assertEquals($decoded->getProperty('secret'), $secret); - } - public function testHash(): void { $secret = 'secret'; From 9346efc24a1fb7b594a49316781b3c88347e1b74 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 19:35:42 +0100 Subject: [PATCH 010/385] Fixed some tests --- app/controllers/api/account.php | 22 +++++++++++----------- app/controllers/api/users.php | 2 +- composer.lock | 8 ++++---- src/Appwrite/Auth/Auth.php | 5 ----- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3ac17e953e..4264300cce 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -269,8 +269,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res $protocol = $request->getProtocol(); $response - ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -968,8 +968,8 @@ App::post('/v1/account/sessions/email') $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1131,8 +1131,8 @@ App::post('/v1/account/sessions/anonymous') $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1750,13 +1750,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') if ($state['success']['path'] == $oauthDefaultSuccess) { $query['project'] = $project->getId(); $query['domain'] = Config::getParam('cookieDomain'); - $query['key'] = Auth::$cookieName; + $query['key'] = $store->getKey(); $query['secret'] = $encoded; } $response - ->addCookie(Auth::$cookieName . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } if (isset($sessionUpgrade) && $sessionUpgrade) { @@ -3161,8 +3161,8 @@ App::patch('/v1/account/status') $protocol = $request->getProtocol(); $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic($user, Response::MODEL_ACCOUNT); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index e1753fd9b4..2fbbc423ba 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1313,7 +1313,7 @@ App::patch('/v1/users/:userId/password') // Create Argon2 hasher with default settings $hasher = new Argon2(); - $hasher->setMemoryCost(65536); + $hasher->setMemoryCost(2048); $hasher->setTimeCost(4); $hasher->setThreads(3); diff --git a/composer.lock b/composer.lock index 1997076f5e..050a92a1e3 100644 --- a/composer.lock +++ b/composer.lock @@ -3512,12 +3512,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "ada77726740eb7180a4cee762cdf0c1beb0dc3a3" + "reference": "966fbfefb27be94e3363f07279787d5cf8a66b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/ada77726740eb7180a4cee762cdf0c1beb0dc3a3", - "reference": "ada77726740eb7180a4cee762cdf0c1beb0dc3a3", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/966fbfefb27be94e3363f07279787d5cf8a66b95", + "reference": "966fbfefb27be94e3363f07279787d5cf8a66b95", "shasum": "" }, "require": { @@ -3559,7 +3559,7 @@ "issues": "https://github.com/utopia-php/auth/issues", "source": "https://github.com/utopia-php/auth/tree/dev" }, - "time": "2025-03-16T16:32:38+00:00" + "time": "2025-03-16T18:32:00+00:00" }, { "name": "utopia-php/cache", diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index ee1233fc10..b0cba3235e 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -98,11 +98,6 @@ class Auth */ public const MFA_RECENT_DURATION = 1800; // 30 mins - /** - * @var string - */ - public static $cookieName = 'a_session'; - /** * @var string */ From f618a8b5630eca0df69939b1ee0ab2f34b450375 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Mar 2025 22:16:02 +0100 Subject: [PATCH 011/385] Fixed account test --- app/controllers/api/account.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4264300cce..9445d937b7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3145,7 +3145,8 @@ App::patch('/v1/account/status') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('store') + ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Store $store) { $user->setAttribute('status', false); From 1ce84f1650f1df409bad72d00ca7995c226cbcb6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 12:39:35 +0100 Subject: [PATCH 012/385] WIP --- app/controllers/api/account.php | 14 +++++++++----- app/controllers/api/teams.php | 11 +++++++---- app/init.php | 21 ++++++++++++++++++++- src/Appwrite/Auth/Auth.php | 14 -------------- src/Appwrite/Platform/Tasks/Install.php | 3 ++- tests/unit/Auth/AuthTest.php | 6 ------ 6 files changed, 38 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9445d937b7..3abedb1f3f 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -38,6 +38,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; +use Utopia\Auth\Proofs\Password as ProofsPassword; use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; @@ -364,7 +365,9 @@ App::post('/v1/account') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $proof = new ProofsPassword(); + $hash = $proof->hash($password); + try { $userId = $userId == 'unique()' ? ID::unique() : $userId; $user->setAttributes([ @@ -377,7 +380,7 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'password' => $password, + 'password' => $hash, 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], 'passwordUpdate' => DateTime::now(), 'hash' => Auth::DEFAULT_ALGO, @@ -942,7 +945,7 @@ App::post('/v1/account/sessions/email') // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('password', (new ProofsPassword())->hash($password)) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $dbForProject->updateDocument('users', $user->getId(), $user); @@ -2930,13 +2933,14 @@ App::patch('/v1/account/email') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { + ->inject('proofForPassword') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); if ( !empty($passwordUpdate) && - !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + !$proofForPassword->verify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 60d6960445..b1b4445de0 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -27,6 +27,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit; +use Utopia\Auth\Proofs\Password; use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; @@ -469,7 +470,8 @@ App::post('/v1/teams/:teamId/memberships') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { + ->inject('proofForPassword') + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword) { $isAppUser = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -542,6 +544,7 @@ App::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); + $hash = $proofForPassword->hash($proofForPassword->generate()); $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ @@ -555,9 +558,9 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => true, // TODO: Set password empty? - 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'password' => $hash, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), /** * Set the password update time to 0 for users created using * team invite and OAuth to allow password updates without an diff --git a/app/init.php b/app/init.php index e54b7b3cc8..ffab067206 100644 --- a/app/init.php +++ b/app/init.php @@ -51,6 +51,9 @@ use PHPMailer\PHPMailer\PHPMailer; use Swoole\Database\PDOProxy; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Proofs\Code; +use Utopia\Auth\Proofs\Password; +use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Adapter\Sharding; @@ -1976,6 +1979,22 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); -App::setResource('store', function () { +App::setResource('store', function (): Store { return new Store(); }); + +App::setResource('proofForPassword', function (): Password { + return new Password(); +}); + +App::setResource('proofForToken', function (): Token { + return new Token(); +}); + +App::setResource('proofForCode', function (): Code { + return new Code(); +}); + + + + diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index b0cba3235e..d47d9ec4b5 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -237,20 +237,6 @@ class Auth } } - /** - * Password Generator. - * - * Generate random password string - * - * @param int $length - * - * @return string - */ - public static function passwordGenerator(int $length = 20): string - { - return \bin2hex(\random_bytes($length)); - } - /** * Token Generator. * diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index c7b1f72453..9b6ec64154 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -6,6 +6,7 @@ use Appwrite\Auth\Auth; use Appwrite\Docker\Compose; use Appwrite\Docker\Env; use Appwrite\Utopia\View; +use Utopia\Auth\Proofs\Password; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Platform\Action; @@ -162,7 +163,7 @@ class Install extends Action } if ($var['filter'] === 'password') { - $input[$var['name']] = Auth::passwordGenerator(); + $input[$var['name']] = (new Password())->generate(); continue; } } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 2b19439da6..c2057394d3 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -163,12 +163,6 @@ class AuthTest extends TestCase $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8')); } - public function testPasswordGenerator(): void - { - $this->assertEquals(\mb_strlen(Auth::passwordGenerator()), 40); - $this->assertEquals(\mb_strlen(Auth::passwordGenerator(5)), 10); - } - public function testTokenGenerator(): void { $this->assertEquals(\strlen(Auth::tokenGenerator()), 256); From fdb83c51f72b93e8b7d65d5c7410af23f7fc5cb0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 12:46:07 +0100 Subject: [PATCH 013/385] formatting --- app/init/resources.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 51b7ff0616..f04deaba89 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -23,6 +23,10 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Proofs\Code; +use Utopia\Auth\Proofs\Password; +use Utopia\Auth\Proofs\Token; +use Utopia\Auth\Store; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; use Utopia\CLI\Console; @@ -48,10 +52,6 @@ use Utopia\Storage\Storage; use Utopia\System\System; use Utopia\Validator\Hostname; use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub; -use Utopia\Auth\Store; -use Utopia\Auth\Proofs\Password; -use Utopia\Auth\Proofs\Token; -use Utopia\Auth\Proofs\Code; // Runtime Execution App::setResource('log', fn () => new Log()); From 8aa57141730d9a8191711aff7b3705e47780340f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 21:44:31 +0100 Subject: [PATCH 014/385] cleanups --- app/config/collections/common.php | 4 +- app/config/console.php | 2 +- app/config/roles.php | 12 +- app/controllers/api/account.php | 171 +++++++------- app/controllers/api/projects.php | 2 +- app/controllers/api/teams.php | 8 +- app/controllers/api/users.php | 10 +- app/controllers/shared/api.php | 10 +- app/controllers/shared/api/auth.php | 2 +- app/init/constants.php | 67 +++++- composer.lock | 34 +-- src/Appwrite/Auth/Auth.php | 217 ++---------------- src/Appwrite/Auth/Hash.php | 62 ----- src/Appwrite/Auth/Key.php | 8 +- .../Auth/Validator/PasswordHistory.php | 6 +- src/Appwrite/Migration/Version/V16.php | 2 +- src/Appwrite/Migration/Version/V17.php | 2 +- src/Appwrite/Migration/Version/V20.php | 10 +- src/Appwrite/Platform/Workers/Audits.php | 2 +- src/Appwrite/Platform/Workers/Deletes.php | 2 +- .../Utopia/Response/Model/Project.php | 4 +- .../Projects/ProjectsConsoleClientTest.php | 6 +- tests/unit/Auth/AuthTest.php | 101 ++++---- tests/unit/Auth/KeyTest.php | 4 +- .../unit/Messaging/MessagingChannelsTest.php | 4 +- 25 files changed, 293 insertions(+), 459 deletions(-) delete mode 100644 src/Appwrite/Auth/Hash.php diff --git a/app/config/collections/common.php b/app/config/collections/common.php index f68400e226..7e7da1c94d 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -173,7 +173,7 @@ return [ 'size' => 256, 'signed' => true, 'required' => false, - 'default' => Auth::DEFAULT_ALGO, + 'default' => '', 'array' => false, 'filters' => [], ], @@ -184,7 +184,7 @@ return [ 'size' => 65535, 'signed' => true, 'required' => false, - 'default' => Auth::DEFAULT_ALGO_OPTIONS, + 'default' => new \stdClass(), 'array' => false, 'filters' => ['json'], ], diff --git a/app/config/console.php b/app/config/console.php index e37c9b7836..a0988ffaf9 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -38,7 +38,7 @@ $console = [ 'mockNumbers' => [], 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds + 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled' ], 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], diff --git a/app/config/roles.php b/app/config/roles.php index a4abee0c45..2c0499855f 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -84,7 +84,7 @@ $admins = [ ]; return [ - Auth::USER_ROLE_GUESTS => [ + USER_ROLE_GUESTS => [ 'label' => 'Guests', 'scopes' => [ 'global', @@ -102,23 +102,23 @@ return [ 'execution.write', ], ], - Auth::USER_ROLE_USERS => [ + USER_ROLE_USERS => [ 'label' => 'Users', 'scopes' => \array_merge($member), ], - Auth::USER_ROLE_ADMIN => [ + USER_ROLE_ADMIN => [ 'label' => 'Admin', 'scopes' => \array_merge($admins), ], - Auth::USER_ROLE_DEVELOPER => [ + USER_ROLE_DEVELOPER => [ 'label' => 'Developer', 'scopes' => \array_merge($admins), ], - Auth::USER_ROLE_OWNER => [ + USER_ROLE_OWNER => [ 'label' => 'Owner', 'scopes' => \array_merge($member, $admins), ], - Auth::USER_ROLE_APPS => [ + USER_ROLE_APPS => [ 'label' => 'Applications', 'scopes' => ['global', 'health.read', 'graphql'], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3abedb1f3f..812b454cac 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -176,17 +176,17 @@ $createSession = function (string $userId, string $secret, Request $request, Res $user->setAttributes($userFromRequest->getArrayCopy()); - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $sessionSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); + $sessionSecret = Auth::tokenGenerator(TOKEN_LENGTH_SESSION); $factor = (match ($verifiedToken->getAttribute('type')) { - Auth::TOKEN_TYPE_MAGIC_URL, - Auth::TOKEN_TYPE_OAUTH2, - Auth::TOKEN_TYPE_EMAIL => Type::EMAIL, - Auth::TOKEN_TYPE_PHONE => Type::PHONE, - Auth::TOKEN_TYPE_GENERIC => 'token', + TOKEN_TYPE_MAGIC_URL, + TOKEN_TYPE_OAUTH2, + TOKEN_TYPE_EMAIL => Type::EMAIL, + TOKEN_TYPE_PHONE => Type::PHONE, + TOKEN_TYPE_GENERIC => 'token', default => throw new Exception(Exception::USER_INVALID_TOKEN) }); @@ -221,11 +221,11 @@ $createSession = function (string $userId, string $secret, Request $request, Res $dbForProject->purgeCachedDocument('users', $user->getId()); // Magic URL + Email OTP - if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_MAGIC_URL || $verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_EMAIL) { + if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_MAGIC_URL || $verifiedToken->getAttribute('type') === TOKEN_TYPE_EMAIL) { $user->setAttribute('emailVerification', true); } - if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_PHONE) { + if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) { $user->setAttribute('phoneVerification', true); } @@ -236,8 +236,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res } $isAllowedTokenType = match ($verifiedToken->getAttribute('type')) { - Auth::TOKEN_TYPE_MAGIC_URL, - Auth::TOKEN_TYPE_EMAIL => false, + TOKEN_TYPE_MAGIC_URL, + TOKEN_TYPE_EMAIL => false, default => true }; @@ -383,8 +383,8 @@ App::post('/v1/account') 'password' => $hash, 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], 'passwordUpdate' => DateTime::now(), - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'hash' => $proof->getHash()->getName(), + 'hashOptions' => $proof->getHash()->getOptions(), 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -821,7 +821,7 @@ App::patch('/v1/account/sessions/:sessionId') } // Extend session - $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $session->setAttribute('expire', DateTime::addSeconds(new \DateTime(), $authDuration)); // Refresh OAuth access token @@ -893,7 +893,8 @@ App::post('/v1/account/sessions/email') ->inject('queueForMails') ->inject('hooks') ->inject('store') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store) { + ->inject('proofForPassword') + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -901,7 +902,9 @@ App::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { + $userProofForPassword = ProofsPassword::createHash($profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); + + if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !$userProofForPassword->verify($password, $profile->getAttribute('password'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -917,16 +920,16 @@ App::post('/v1/account/sessions/email') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_SESSION); $session = new Document(array_merge( [ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => $email, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -943,11 +946,11 @@ App::post('/v1/account/sessions/email') Authorization::setRole(Role::user($user->getId())->toString()); // Re-hash if not using recommended algo - if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { + if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) { $user ->setAttribute('password', (new ProofsPassword())->hash($password)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); + ->setAttribute('hash', $proofForPassword->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()); $dbForProject->updateDocument('users', $user->getId(), $user); } @@ -1033,7 +1036,8 @@ App::post('/v1/account/sessions/anonymous') ->inject('geodb') ->inject('queueForEvents') ->inject('store') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store) { + ->inject('proofForPassword') + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword) { $protocol = $request->getProtocol(); $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ -1065,8 +1069,8 @@ App::post('/v1/account/sessions/anonymous') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -1084,17 +1088,17 @@ App::post('/v1/account/sessions/anonymous') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); // Create session token - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_SESSION); $session = new Document(array_merge( [ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, + 'provider' => SESSION_PROVIDER_ANONYMOUS, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -1350,7 +1354,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('geodb') ->inject('queueForEvents') ->inject('store') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store) use ($oauthDefaultSuccess) { + ->inject('proofForPassword') + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword) use ($oauthDefaultSuccess) { $protocol = $request->getProtocol(); $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; @@ -1568,8 +1573,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => true, // Email should already be authenticated by OAuth2 provider 'password' => null, - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -1668,17 +1673,17 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); // If the `token` param is set, we will return the token in the query string if ($state['token']) { - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_OAUTH2); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_OAUTH2); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_OAUTH2, + 'type' => TOKEN_TYPE_OAUTH2, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -1707,7 +1712,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } else { $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_SESSION); $session = new Document(array_merge([ '$id' => ID::unique(), @@ -1902,7 +1907,8 @@ App::post('/v1/account/tokens/magic-url') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { + ->inject('proofForPassword') + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -1951,8 +1957,8 @@ App::post('/v1/account/tokens/magic-url') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -1970,14 +1976,14 @@ App::post('/v1/account/tokens/magic-url') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); + $tokenSecret = Auth::tokenGenerator(TOKEN_LENGTH_MAGIC_URL); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_MAGIC_URL, + 'type' => TOKEN_TYPE_MAGIC_URL, 'secret' => Auth::hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -2150,7 +2156,8 @@ App::post('/v1/account/tokens/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { + ->inject('proofForPassword') + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2198,8 +2205,8 @@ App::post('/v1/account/tokens/email') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -2216,13 +2223,13 @@ App::post('/v1/account/tokens/email') } $tokenSecret = Auth::codeGenerator(6); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP)); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_EMAIL, + 'type' => TOKEN_TYPE_EMAIL, 'secret' => Auth::hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -2549,13 +2556,13 @@ App::post('/v1/account/tokens/phone') } $secret ??= Auth::codeGenerator(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP)); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_PHONE, + 'type' => TOKEN_TYPE_PHONE, 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -2861,14 +2868,16 @@ App::patch('/v1/account/password') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->inject('proofForPassword') + ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword) { + $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); // Check old password only if its an existing user. - if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password + if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $newPassword = $proofForPassword->hash($password); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $user->getAttribute('passwordHistory', []); if ($historyLimit > 0) { @@ -2894,8 +2903,8 @@ App::patch('/v1/account/password') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); + ->setAttribute('hash', $proofForPassword->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()); $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); @@ -2938,9 +2947,11 @@ App::patch('/v1/account/email') // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); + $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); + if ( !empty($passwordUpdate) && - !$proofForPassword->verify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + !$userProofForPassword->verify($password, $user->getAttribute('password')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -2967,9 +2978,9 @@ App::patch('/v1/account/email') if (empty($passwordUpdate)) { $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('password', $proofForPassword->hash($password)) + ->setAttribute('hash', $proofForPassword->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) ->setAttribute('passwordUpdate', DateTime::now()); } @@ -3030,13 +3041,16 @@ App::patch('/v1/account/phone') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { + ->inject('proofForPassword') + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); + $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); + if ( !empty($passwordUpdate) && - !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + !$userProofForPassword->verify($password, $user->getAttribute('password')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -3060,9 +3074,9 @@ App::patch('/v1/account/phone') if (empty($passwordUpdate)) { $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('password', $proofForPassword->hash($password)) + ->setAttribute('hash', $proofForPassword->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) ->setAttribute('passwordUpdate', DateTime::now()); } @@ -3233,14 +3247,14 @@ App::post('/v1/account/recovery') throw new Exception(Exception::USER_BLOCKED); } - $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_RECOVERY); + $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_RECOVERY); - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_RECOVERY); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_RECOVERY); $recovery = new Document([ '$id' => ID::unique(), 'userId' => $profile->getId(), 'userInternalId' => $profile->getInternalId(), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -3391,7 +3405,8 @@ App::put('/v1/account/recovery') ->inject('project') ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) { + ->inject('proofForPassword') + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3399,7 +3414,7 @@ App::put('/v1/account/recovery') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret); + $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_RECOVERY, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3407,7 +3422,7 @@ App::put('/v1/account/recovery') Authorization::setRole(Role::user($profile->getId())->toString()); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $newPassword = $proofForPassword->hash($password); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $profile->getAttribute('passwordHistory', []); @@ -3427,8 +3442,8 @@ App::put('/v1/account/recovery') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('hash', $proofForPassword->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) ->setAttribute('emailVerification', true)); $user->setAttributes($profile->getArrayCopy()); @@ -3495,14 +3510,14 @@ App::post('/v1/account/verification') $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); - $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); - $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); + $verificationSecret = Auth::tokenGenerator(TOKEN_LENGTH_VERIFICATION); + $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_VERIFICATION, + 'type' => TOKEN_TYPE_VERIFICATION, 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -3658,7 +3673,7 @@ App::put('/v1/account/verification') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret); + $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_VERIFICATION, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3750,13 +3765,13 @@ App::post('/v1/account/verification/phone') } $secret ??= Auth::codeGenerator(); - $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); + $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_PHONE, + 'type' => TOKEN_TYPE_PHONE, 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -3880,7 +3895,7 @@ App::put('/v1/account/verification/phone') throw new Exception(Exception::USER_NOT_FOUND); } - $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_PHONE, $secret); + $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), TOKEN_TYPE_PHONE, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -4354,7 +4369,7 @@ App::post('/v1/account/mfa/challenge') ->inject('plan') ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { - $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); + $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); $code = Auth::codeGenerator(); $challenge = new Document([ 'userId' => $user->getId(), diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 48d20cd17f..bb6055f1e8 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -117,7 +117,7 @@ App::post('/v1/projects') 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, + 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false, 'mockNumbers' => [], 'sessionAlerts' => false, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index b1b4445de0..b406bd03d7 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -451,7 +451,7 @@ App::post('/v1/teams/:teamId/memberships') ; $roles = array_keys(Config::getParam('roles', [])); array_filter($roles, function ($role) { - return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); + return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]); }); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); } @@ -1038,7 +1038,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ; $roles = array_keys(Config::getParam('roles', [])); array_filter($roles, function ($role) { - return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); + return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]); }); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); } @@ -1184,7 +1184,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::addSeconds(new \DateTime(), $authDuration); $secret = Auth::tokenGenerator(); $session = new Document(array_merge([ @@ -1196,7 +1196,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ], 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => $user->getAttribute('email'), 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 2fbbc423ba..1be2cdf236 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2022,11 +2022,11 @@ App::post('/v1/users/:userId/sessions') throw new Exception(Exception::USER_NOT_FOUND); } - $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); + $secret = Auth::tokenGenerator(TOKEN_LENGTH_SESSION); $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $session = new Document(array_merge( @@ -2034,7 +2034,7 @@ App::post('/v1/users/:userId/sessions') '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'provider' => Auth::SESSION_PROVIDER_SERVER, + 'provider' => SESSION_PROVIDER_SERVER, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'factors' => ['server'], @@ -2099,7 +2099,7 @@ App::post('/v1/users/:userId/tokens') )) ->param('userId', '', new UID(), 'User ID.') ->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true) - ->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true) + ->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true) ->inject('request') ->inject('response') ->inject('dbForProject') @@ -2118,7 +2118,7 @@ App::post('/v1/users/:userId/tokens') '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'type' => Auth::TOKEN_TYPE_GENERIC, + 'type' => TOKEN_TYPE_GENERIC, 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 4fcdc12017..dfec602932 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -282,11 +282,11 @@ App::init() Authorization::setDefaultStatus(false); // Handle special app role case - if ($apiKey->getRole() === Auth::USER_ROLE_APPS) { + if ($apiKey->getRole() === USER_ROLE_APPS) { $user = new Document([ '$id' => '', 'status' => true, - 'type' => Auth::ACTIVITY_TYPE_APP, + 'type' => ACTIVITY_TYPE_APP, 'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(), 'password' => '', 'name' => $apiKey->getName(), @@ -551,7 +551,7 @@ App::init() if (!$user->isEmpty()) { $userClone = clone $user; // $user doesn't support `type` and can cause unintended effects. - $userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER); + $userClone->setAttribute('type', ACTIVITY_TYPE_USER); $queueForAudits->setUser($userClone); } @@ -773,7 +773,7 @@ App::shutdown() if (!$user->isEmpty()) { $userClone = clone $user; // $user doesn't support `type` and can cause unintended effects. - $userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER); + $userClone->setAttribute('type', ACTIVITY_TYPE_USER); $queueForAudits->setUser($userClone); } elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) { /** @@ -787,7 +787,7 @@ App::shutdown() $user = new Document([ '$id' => '', 'status' => true, - 'type' => Auth::ACTIVITY_TYPE_GUEST, + 'type' => ACTIVITY_TYPE_GUEST, 'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(), 'password' => '', 'name' => 'Guest', diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index ecabc641ec..8f5e981362 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -20,7 +20,7 @@ App::init() $lastUpdate = $session->getAttribute('mfaUpdatedAt'); if (!empty($lastUpdate)) { $now = DateTime::now(); - $maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge + $maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge $isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now); } diff --git a/app/init/constants.php b/app/init/constants.php index d46d3ed79c..44f8a36562 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -72,6 +72,72 @@ const APP_PLATFORM_SERVER = 'server'; const APP_PLATFORM_CLIENT = 'client'; const APP_PLATFORM_CONSOLE = 'console'; +// User Roles +const USER_ROLE_ANY = 'any'; +const USER_ROLE_GUESTS = 'guests'; +const USER_ROLE_USERS = 'users'; +const USER_ROLE_ADMIN = 'admin'; +const USER_ROLE_DEVELOPER = 'developer'; +const USER_ROLE_OWNER = 'owner'; +const USER_ROLE_APPS = 'apps'; +const USER_ROLE_SYSTEM = 'system'; + +/** + * Token Expiration times. + */ +const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */ +const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */ +const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */ +const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */ +const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */ +const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */ + +/** + * Token Lengths. + */ +const TOKEN_LENGTH_MAGIC_URL = 64; +const TOKEN_LENGTH_VERIFICATION = 256; +const TOKEN_LENGTH_RECOVERY = 256; +const TOKEN_LENGTH_OAUTH2 = 64; +const TOKEN_LENGTH_SESSION = 256; + +/** + * Token Types. + */ +const TOKEN_TYPE_LOGIN = 1; // Deprecated +const TOKEN_TYPE_VERIFICATION = 2; +const TOKEN_TYPE_RECOVERY = 3; +const TOKEN_TYPE_INVITE = 4; +const TOKEN_TYPE_MAGIC_URL = 5; +const TOKEN_TYPE_PHONE = 6; +const TOKEN_TYPE_OAUTH2 = 7; +const TOKEN_TYPE_GENERIC = 8; +const TOKEN_TYPE_EMAIL = 9; // OTP + +/** + * Session Providers. + */ +const SESSION_PROVIDER_EMAIL = 'email'; +const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; +const SESSION_PROVIDER_MAGIC_URL = 'magic-url'; +const SESSION_PROVIDER_PHONE = 'phone'; +const SESSION_PROVIDER_OAUTH2 = 'oauth2'; +const SESSION_PROVIDER_TOKEN = 'token'; +const SESSION_PROVIDER_SERVER = 'server'; + +/** + * Activity associated with user or the app. + */ +const ACTIVITY_TYPE_APP = 'app'; +const ACTIVITY_TYPE_USER = 'user'; +const ACTIVITY_TYPE_GUEST = 'guest'; + +/** + * MFA + */ +const MFA_RECENT_DURATION = 1800; // 30 mins + + // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; @@ -244,7 +310,6 @@ const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage'; // Resource types - const RESOURCE_TYPE_PROJECTS = 'projects'; const RESOURCE_TYPE_FUNCTIONS = 'functions'; const RESOURCE_TYPE_SITES = 'sites'; diff --git a/composer.lock b/composer.lock index 050a92a1e3..f3377c1fec 100644 --- a/composer.lock +++ b/composer.lock @@ -3512,12 +3512,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "966fbfefb27be94e3363f07279787d5cf8a66b95" + "reference": "ed49b9e481030ba5e589140b41a9f4be1486310f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/966fbfefb27be94e3363f07279787d5cf8a66b95", - "reference": "966fbfefb27be94e3363f07279787d5cf8a66b95", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/ed49b9e481030ba5e589140b41a9f4be1486310f", + "reference": "ed49b9e481030ba5e589140b41a9f4be1486310f", "shasum": "" }, "require": { @@ -3559,7 +3559,7 @@ "issues": "https://github.com/utopia-php/auth/issues", "source": "https://github.com/utopia-php/auth/tree/dev" }, - "time": "2025-03-16T18:32:00+00:00" + "time": "2025-03-17T19:57:57+00:00" }, { "name": "utopia-php/cache", @@ -4860,16 +4860,16 @@ }, { "name": "utopia-php/telemetry", - "version": "0.1.0", + "version": "0.1.1", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80" + "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/437f0021777f0e575dfb9e8a1a081b3aed75e33f", + "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f", "shasum": "" }, "require": { @@ -4890,7 +4890,7 @@ "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\Telemetry\\": "src/Telemetry" } }, "notification-url": "https://packagist.org/downloads/", @@ -4904,9 +4904,9 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.1.0" + "source": "https://github.com/utopia-php/telemetry/tree/0.1.1" }, - "time": "2024-11-13T10:29:53+00:00" + "time": "2025-03-17T11:57:52+00:00" }, { "name": "utopia-php/vcs", @@ -5143,16 +5143,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.7", + "version": "0.40.9", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "9e89b0bc4d8e6c81817d27096629f34a149fa873" + "reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9e89b0bc4d8e6c81817d27096629f34a149fa873", - "reference": "9e89b0bc4d8e6c81817d27096629f34a149fa873", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/dbb45a5db22cdc3368fe2573c07ba6088f188fa4", + "reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4", "shasum": "" }, "require": { @@ -5188,9 +5188,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.7" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.9" }, - "time": "2025-03-12T08:43:55+00:00" + "time": "2025-03-17T18:39:14+00:00" }, { "name": "doctrine/annotations", diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index d47d9ec4b5..18b863b15b 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -2,13 +2,6 @@ namespace Appwrite\Auth; -use Appwrite\Auth\Hash\Argon2; -use Appwrite\Auth\Hash\Bcrypt; -use Appwrite\Auth\Hash\Md5; -use Appwrite\Auth\Hash\Phpass; -use Appwrite\Auth\Hash\Scrypt; -use Appwrite\Auth\Hash\Scryptmodified; -use Appwrite\Auth\Hash\Sha; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; @@ -17,87 +10,6 @@ use Utopia\Database\Validator\Roles; class Auth { - public const SUPPORTED_ALGOS = [ - 'argon2', - 'bcrypt', - 'md5', - 'sha', - 'phpass', - 'scrypt', - 'scryptMod', - 'plaintext' - ]; - - public const DEFAULT_ALGO = 'argon2'; - public const DEFAULT_ALGO_OPTIONS = ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3]; - - /** - * User Roles. - */ - public const USER_ROLE_ANY = 'any'; - public const USER_ROLE_GUESTS = 'guests'; - public const USER_ROLE_USERS = 'users'; - public const USER_ROLE_ADMIN = 'admin'; - public const USER_ROLE_DEVELOPER = 'developer'; - public const USER_ROLE_OWNER = 'owner'; - public const USER_ROLE_APPS = 'apps'; - public const USER_ROLE_SYSTEM = 'system'; - - /** - * Activity associated with user or the app. - */ - public const ACTIVITY_TYPE_APP = 'app'; - public const ACTIVITY_TYPE_USER = 'user'; - public const ACTIVITY_TYPE_GUEST = 'guest'; - - /** - * Token Types. - */ - public const TOKEN_TYPE_LOGIN = 1; // Deprecated - public const TOKEN_TYPE_VERIFICATION = 2; - public const TOKEN_TYPE_RECOVERY = 3; - public const TOKEN_TYPE_INVITE = 4; - public const TOKEN_TYPE_MAGIC_URL = 5; - public const TOKEN_TYPE_PHONE = 6; - public const TOKEN_TYPE_OAUTH2 = 7; - public const TOKEN_TYPE_GENERIC = 8; - public const TOKEN_TYPE_EMAIL = 9; // OTP - - /** - * Session Providers. - */ - public const SESSION_PROVIDER_EMAIL = 'email'; - public const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; - public const SESSION_PROVIDER_MAGIC_URL = 'magic-url'; - public const SESSION_PROVIDER_PHONE = 'phone'; - public const SESSION_PROVIDER_OAUTH2 = 'oauth2'; - public const SESSION_PROVIDER_TOKEN = 'token'; - public const SESSION_PROVIDER_SERVER = 'server'; - - /** - * Token Expiration times. - */ - public const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */ - public const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */ - public const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */ - public const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */ - public const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */ - public const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */ - - /** - * Token Lengths. - */ - public const TOKEN_LENGTH_MAGIC_URL = 64; - public const TOKEN_LENGTH_VERIFICATION = 256; - public const TOKEN_LENGTH_RECOVERY = 256; - public const TOKEN_LENGTH_OAUTH2 = 64; - public const TOKEN_LENGTH_SESSION = 256; - - /** - * MFA - */ - public const MFA_RECENT_DURATION = 1800; // 30 mins - /** * @var string */ @@ -109,18 +21,18 @@ class Auth public static function getSessionProviderByTokenType(int $type): string { switch ($type) { - case Auth::TOKEN_TYPE_VERIFICATION: - case Auth::TOKEN_TYPE_RECOVERY: - case Auth::TOKEN_TYPE_INVITE: - return Auth::SESSION_PROVIDER_EMAIL; - case Auth::TOKEN_TYPE_MAGIC_URL: - return Auth::SESSION_PROVIDER_MAGIC_URL; - case Auth::TOKEN_TYPE_PHONE: - return Auth::SESSION_PROVIDER_PHONE; - case Auth::TOKEN_TYPE_OAUTH2: - return Auth::SESSION_PROVIDER_OAUTH2; + case TOKEN_TYPE_VERIFICATION: + case TOKEN_TYPE_RECOVERY: + case TOKEN_TYPE_INVITE: + return SESSION_PROVIDER_EMAIL; + case TOKEN_TYPE_MAGIC_URL: + return SESSION_PROVIDER_MAGIC_URL; + case TOKEN_TYPE_PHONE: + return SESSION_PROVIDER_PHONE; + case TOKEN_TYPE_OAUTH2: + return SESSION_PROVIDER_OAUTH2; default: - return Auth::SESSION_PROVIDER_TOKEN; + return SESSION_PROVIDER_TOKEN; } } @@ -138,105 +50,6 @@ class Auth return \hash('sha256', $string); } - /** - * Password Hash. - * - * One way string hashing for user passwords - * - * @param string $string - * @param string $algo hashing algorithm to use - * @param array $options algo-specific options - * - * @return bool|string|null - */ - public static function passwordHash(string $string, string $algo, array $options = []) - { - // Plain text not supported, just an alias. Switch to recommended algo - if ($algo === 'plaintext') { - $algo = Auth::DEFAULT_ALGO; - $options = Auth::DEFAULT_ALGO_OPTIONS; - } - - if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) { - throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); - } - - switch ($algo) { - case 'argon2': - $hasher = new Argon2($options); - return $hasher->hash($string); - case 'bcrypt': - $hasher = new Bcrypt($options); - return $hasher->hash($string); - case 'md5': - $hasher = new Md5($options); - return $hasher->hash($string); - case 'sha': - $hasher = new Sha($options); - return $hasher->hash($string); - case 'phpass': - $hasher = new Phpass($options); - return $hasher->hash($string); - case 'scrypt': - $hasher = new Scrypt($options); - return $hasher->hash($string); - case 'scryptMod': - $hasher = new Scryptmodified($options); - return $hasher->hash($string); - default: - throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); - } - } - - /** - * Password verify. - * - * @param string $plain - * @param string $hash - * @param string $algo hashing algorithm used to hash - * @param array $options algo-specific options - * - * @return bool - */ - public static function passwordVerify(string $plain, string $hash, string $algo, array $options = []) - { - // Plain text not supported, just an alias. Switch to recommended algo - if ($algo === 'plaintext') { - $algo = Auth::DEFAULT_ALGO; - $options = Auth::DEFAULT_ALGO_OPTIONS; - } - - if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) { - throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); - } - - switch ($algo) { - case 'argon2': - $hasher = new Argon2($options); - return $hasher->verify($plain, $hash); - case 'bcrypt': - $hasher = new Bcrypt($options); - return $hasher->verify($plain, $hash); - case 'md5': - $hasher = new Md5($options); - return $hasher->verify($plain, $hash); - case 'sha': - $hasher = new Sha($options); - return $hasher->verify($plain, $hash); - case 'phpass': - $hasher = new Phpass($options); - return $hasher->verify($plain, $hash); - case 'scrypt': - $hasher = new Scrypt($options); - return $hasher->verify($plain, $hash); - case 'scryptMod': - $hasher = new Scryptmodified($options); - return $hasher->verify($plain, $hash); - default: - throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); - } - } - /** * Token Generator. * @@ -339,9 +152,9 @@ class Auth public static function isPrivilegedUser(array $roles): bool { if ( - in_array(self::USER_ROLE_OWNER, $roles) || - in_array(self::USER_ROLE_DEVELOPER, $roles) || - in_array(self::USER_ROLE_ADMIN, $roles) + in_array(USER_ROLE_OWNER, $roles) || + in_array(USER_ROLE_DEVELOPER, $roles) || + in_array(USER_ROLE_ADMIN, $roles) ) { return true; } @@ -358,7 +171,7 @@ class Auth */ public static function isAppUser(array $roles): bool { - if (in_array(self::USER_ROLE_APPS, $roles)) { + if (in_array(USER_ROLE_APPS, $roles)) { return true; } diff --git a/src/Appwrite/Auth/Hash.php b/src/Appwrite/Auth/Hash.php deleted file mode 100644 index 7134057581..0000000000 --- a/src/Appwrite/Auth/Hash.php +++ /dev/null @@ -1,62 +0,0 @@ -setOptions($options); - } - - /** - * Set hashing algo options - * - * @param array $options Hashing-algo specific options - */ - public function setOptions(array $options): self - { - $this->options = \array_merge([], $this->getDefaultOptions(), $options); - return $this; - } - - /** - * Get hashing algo options - * - * @return array $options Hashing-algo specific options - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * @param string $password Input password to hash - * - * @return string hash - */ - abstract public function hash(string $password): string; - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - abstract public function verify(string $password, string $hash): bool; - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - abstract public function getDefaultOptions(): array; -} diff --git a/src/Appwrite/Auth/Key.php b/src/Appwrite/Auth/Key.php index 89c28c4727..fb6d2ceafe 100644 --- a/src/Appwrite/Auth/Key.php +++ b/src/Appwrite/Auth/Key.php @@ -104,16 +104,16 @@ class Key $secret = $key; } - $role = Auth::USER_ROLE_APPS; + $role = USER_ROLE_APPS; $roles = Config::getParam('roles', []); - $scopes = $roles[Auth::USER_ROLE_APPS]['scopes'] ?? []; + $scopes = $roles[USER_ROLE_APPS]['scopes'] ?? []; $expired = false; $guestKey = new Key( $project->getId(), $type, - Auth::USER_ROLE_GUESTS, - $roles[Auth::USER_ROLE_GUESTS]['scopes'] ?? [], + USER_ROLE_GUESTS, + $roles[USER_ROLE_GUESTS]['scopes'] ?? [], 'UNKNOWN' ); diff --git a/src/Appwrite/Auth/Validator/PasswordHistory.php b/src/Appwrite/Auth/Validator/PasswordHistory.php index f623ca180d..7677deafc0 100644 --- a/src/Appwrite/Auth/Validator/PasswordHistory.php +++ b/src/Appwrite/Auth/Validator/PasswordHistory.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Appwrite\Auth\Auth; +use Utopia\Auth\Proofs\Password as ProofsPassword; /** * Password. @@ -45,8 +45,10 @@ class PasswordHistory extends Password */ public function isValid($value): bool { + $proofForPassword = ProofsPassword::createHash($this->algo, $this->algoOptions); + foreach ($this->history as $hash) { - if (!empty($hash) && Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) { + if (!empty($hash) && $proofForPassword->verify($value, $hash)) { return false; } } diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 49f244598e..203505ce26 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -118,7 +118,7 @@ class V16 extends Migration * Set default authDuration */ $document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [ - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG + 'duration' => TOKEN_EXPIRATION_LOGIN_LONG ])); /** diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index 96c890c65d..46b4715a65 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -270,7 +270,7 @@ class V17 extends Migration * Set hashOptions type */ $document->setAttribute('hashOptions', array_merge($document->getAttribute('hashOptions', []), [ - 'type' => $document->getAttribute('hash', Auth::DEFAULT_ALGO) + 'type' => $document->getAttribute('hash', 'argon2') ])); break; } diff --git a/src/Appwrite/Migration/Version/V20.php b/src/Appwrite/Migration/Version/V20.php index 5a0807cedf..93115ed5ae 100644 --- a/src/Appwrite/Migration/Version/V20.php +++ b/src/Appwrite/Migration/Version/V20.php @@ -632,15 +632,15 @@ class V20 extends Migration } break; case 'sessions': - $duration = $this->project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $this->project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::addSeconds(new \DateTime(), $duration); $document->setAttribute('expire', $expire); $factors = match ($document->getAttribute('provider')) { - Auth::SESSION_PROVIDER_EMAIL => ['password'], - Auth::SESSION_PROVIDER_PHONE => ['phone'], - Auth::SESSION_PROVIDER_ANONYMOUS => ['anonymous'], - Auth::SESSION_PROVIDER_TOKEN => ['token'], + SESSION_PROVIDER_EMAIL => ['password'], + SESSION_PROVIDER_PHONE => ['phone'], + SESSION_PROVIDER_ANONYMOUS => ['anonymous'], + SESSION_PROVIDER_TOKEN => ['token'], default => ['email'], }; diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index ed5ff8010a..c605d78b27 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -83,7 +83,7 @@ class Audits extends Action $userName = $user->getAttribute('name', ''); $userEmail = $user->getAttribute('email', ''); - $userType = $user->getAttribute('type', Auth::ACTIVITY_TYPE_USER); + $userType = $user->getAttribute('type', ACTIVITY_TYPE_USER); // Create event data $eventData = [ diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 95d58f8003..22d40f83fa 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -716,7 +716,7 @@ class Deletes extends Action private function deleteExpiredSessions(Document $project, callable $getProjectDB): void { $dbForProject = $getProjectDB($project); - $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expired = DateTime::addSeconds(new \DateTime(), -1 * $duration); // Delete Sessions diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index fbbe062531..367796f5f4 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -105,7 +105,7 @@ class Project extends Model ->addRule('authDuration', [ 'type' => self::TYPE_INTEGER, 'description' => 'Session duration in seconds.', - 'default' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, + 'default' => TOKEN_EXPIRATION_LOGIN_LONG, 'example' => 60, ]) ->addRule('authLimit', [ @@ -359,7 +359,7 @@ class Project extends Model $auth = Config::getParam('auth', []); $document->setAttribute('authLimit', $authValues['limit'] ?? 0); - $document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG); + $document->setAttribute('authDuration', $authValues['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG); $document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); $document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0); $document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false); diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index ed9171e46a..9e15b632bd 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -787,7 +787,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year + $this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year /** * Test for SUCCESS @@ -931,7 +931,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, + 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -944,7 +944,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year + $this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year return ['projectId' => $projectId]; } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index c2057394d3..e8cf938ce9 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; use PHPUnit\Framework\TestCase; +use Utopia\Auth\Proofs\Password; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -37,7 +38,7 @@ class AuthTest extends TestCase // Bcrypt - Version Y $plain = 'secret'; $hash = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy'; - $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $generatedHash = Password::createHash('bcrypt')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); @@ -45,7 +46,7 @@ class AuthTest extends TestCase // Bcrypt - Version A $plain = 'test123'; $hash = '$2a$12$3f2ZaARQ1AmhtQWx2nmQpuXcWfTj1YV2/Hl54e8uKxIzJe3IfwLiu'; - $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $generatedHash = Password::createHash('bcrypt')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); @@ -53,7 +54,7 @@ class AuthTest extends TestCase // Bcrypt - Cost 5 $plain = 'hello-world'; $hash = '$2a$05$IjrtSz6SN7UJ6Sh3l.b5jODEvEG2LMJTPAHIaLWRvlWx7if3VMkFO'; - $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $generatedHash = Password::createHash('bcrypt')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); @@ -61,7 +62,7 @@ class AuthTest extends TestCase // Bcrypt - Cost 15 $plain = 'super-secret-password'; $hash = '$2a$15$DS0ZzbsFZYumH/E4Qj5oeOHnBcM3nCCsCA2m4Goigat/0iMVQC4Na'; - $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $generatedHash = Password::createHash('bcrypt')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); @@ -69,7 +70,7 @@ class AuthTest extends TestCase // MD5 - Short $plain = 'appwrite'; $hash = '144fa7eaa4904e8ee120651997f70dcc'; - $generatedHash = Auth::passwordHash($plain, 'md5'); + $generatedHash = Password::createHash('md5')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); @@ -77,7 +78,7 @@ class AuthTest extends TestCase // MD5 - Long $plain = 'AppwriteIsAwesomeBackendAsAServiceThatIsAlsoOpenSourced'; $hash = '8410e96cf7ac64e0b84c3f8517a82616'; - $generatedHash = Auth::passwordHash($plain, 'md5'); + $generatedHash = Password::createHash('md5')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); @@ -85,7 +86,7 @@ class AuthTest extends TestCase // PHPass $plain = 'pass123'; $hash = '$P$BVKPmJBZuLch27D4oiMRTEykGLQ9tX0'; - $generatedHash = Auth::passwordHash($plain, 'phpass'); + $generatedHash = Password::createHash('phpass')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); @@ -93,7 +94,7 @@ class AuthTest extends TestCase // SHA $plain = 'developersAreAwesome!'; $hash = '2455118438cb125354b89bb5888346e9bd23355462c40df393fab514bf2220b5a08e4e2d7b85d7327595a450d0ac965cc6661152a46a157c66d681bed20a4735'; - $generatedHash = Auth::passwordHash($plain, 'sha'); + $generatedHash = Password::createHash('sha')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'sha')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'sha')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'sha')); @@ -101,7 +102,7 @@ class AuthTest extends TestCase // Argon2 $plain = 'safe-argon-password'; $hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8'; - $generatedHash = Auth::passwordHash($plain, 'argon2'); + $generatedHash = Password::createHash('argon2')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'argon2')); @@ -109,7 +110,7 @@ class AuthTest extends TestCase // Scrypt $plain = 'some-scrypt-password'; $hash = 'b448ad7ba88b653b5b56b8053a06806724932d0751988bc9cd0ef7ff059e8ba8a020e1913b7069a650d3f99a1559aba0221f2c277826919513a054e76e339028'; - $generatedHash = Auth::passwordHash($plain, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]); + $generatedHash = Password::createHash('scrypt')->setOptions([ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); @@ -126,7 +127,7 @@ class AuthTest extends TestCase // Provider #1 (Database) $plain = 'example-password'; $hash = '$2a$10$3bIGRWUes86CICsuchGLj.e.BqdCdg2/1Ud9LvBhJr0j7Dze8PBdS'; - $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $generatedHash = Password::createHash('bcrypt')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); @@ -134,7 +135,7 @@ class AuthTest extends TestCase // Provider #2 (Blog) $plain = 'your-password'; $hash = '$P$BkiNDJTpAWXtpaMhEUhUdrv7M0I1g6.'; - $generatedHash = Auth::passwordHash($plain, 'phpass'); + $generatedHash = Password::createHash('phpass')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); @@ -147,7 +148,7 @@ class AuthTest extends TestCase $signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ=='; $options = [ 'salt' => $salt, 'saltSeparator' => $saltSeparator, 'signerKey' => $signerKey ]; - $generatedHash = Auth::passwordHash($plain, 'scryptMod', $options); + $generatedHash = Password::createHash('scryptMod')->hash($plain, $options); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scryptMod', $options)); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scryptMod', $options)); $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scryptMod', $options)); @@ -159,7 +160,7 @@ class AuthTest extends TestCase // Bcrypt - Cost 5 $plain = 'whatIsMd8?!?'; - $generatedHash = Auth::passwordHash($plain, 'md8'); + $generatedHash = Password::createHash('md8')->hash($plain); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8')); } @@ -187,14 +188,14 @@ class AuthTest extends TestCase new Document([ '$id' => ID::custom('token1'), 'secret' => $hash, - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1), ]), new Document([ '$id' => ID::custom('token2'), 'secret' => 'secret2', - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1), ]), @@ -206,14 +207,14 @@ class AuthTest extends TestCase new Document([ // Correct secret and type time, wrong expire time '$id' => ID::custom('token1'), 'secret' => $hash, - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2), ]), new Document([ '$id' => ID::custom('token2'), 'secret' => 'secret2', - 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2), ]), @@ -232,13 +233,13 @@ class AuthTest extends TestCase $tokens1 = [ new Document([ '$id' => ID::custom('token1'), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), @@ -247,13 +248,13 @@ class AuthTest extends TestCase $tokens2 = [ new Document([ // Correct secret and type time, wrong expire time '$id' => ID::custom('token1'), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), @@ -262,25 +263,25 @@ class AuthTest extends TestCase $tokens3 = [ // Correct secret and expire time, wrong type new Document([ '$id' => ID::custom('token1'), - 'type' => Auth::TOKEN_TYPE_INVITE, + 'type' => TOKEN_TYPE_INVITE, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'type' => TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), ]; - $this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, $secret), $tokens1[0]); + $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, $secret), $tokens1[0]); $this->assertEquals(Auth::tokenVerify($tokens1, null, $secret), $tokens1[0]); - $this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); - $this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, $secret), false); - $this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); - $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, $secret), false); - $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, $secret), false); + $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, $secret), false); + $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, 'false-secret'), false); } public function testIsPrivilegedUser(): void @@ -288,16 +289,16 @@ class AuthTest extends TestCase $this->assertEquals(false, Auth::isPrivilegedUser([])); $this->assertEquals(false, Auth::isPrivilegedUser([Role::guests()->toString()])); $this->assertEquals(false, Auth::isPrivilegedUser([Role::users()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_ADMIN])); - $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_DEVELOPER])); - $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER])); - $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_SYSTEM])); + $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_ADMIN])); + $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_DEVELOPER])); + $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER])); + $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_SYSTEM])); - $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Role::guests()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER])); + $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, Role::guests()->toString()])); + $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, Role::guests()->toString()])); + $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER])); } public function testIsAppUser(): void @@ -305,16 +306,16 @@ class AuthTest extends TestCase $this->assertEquals(false, Auth::isAppUser([])); $this->assertEquals(false, Auth::isAppUser([Role::guests()->toString()])); $this->assertEquals(false, Auth::isAppUser([Role::users()->toString()])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_ADMIN])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_DEVELOPER])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER])); - $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_SYSTEM])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_ADMIN])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_DEVELOPER])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER])); + $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_SYSTEM])); - $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS])); - $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Role::guests()->toString()])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()])); - $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER])); + $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, USER_ROLE_APPS])); + $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, Role::guests()->toString()])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, Role::guests()->toString()])); + $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER])); } public function testGuestRoles(): void @@ -394,7 +395,7 @@ class AuthTest extends TestCase public function testPrivilegedUserRoles(): void { - Authorization::setRole(Auth::USER_ROLE_OWNER); + Authorization::setRole(USER_ROLE_OWNER); $user = new Document([ '$id' => ID::custom('123'), 'emailVerification' => true, @@ -438,7 +439,7 @@ class AuthTest extends TestCase public function testAppUserRoles(): void { - Authorization::setRole(Auth::USER_ROLE_APPS); + Authorization::setRole(USER_ROLE_APPS); $user = new Document([ '$id' => ID::custom('123'), 'memberships' => [ diff --git a/tests/unit/Auth/KeyTest.php b/tests/unit/Auth/KeyTest.php index 8ae2114697..5ca6135dd0 100644 --- a/tests/unit/Auth/KeyTest.php +++ b/tests/unit/Auth/KeyTest.php @@ -21,7 +21,7 @@ class KeyTest extends TestCase 'collections.read', 'documents.read', ]; - $roleScopes = Config::getParam('roles', [])[Auth::USER_ROLE_APPS]['scopes']; + $roleScopes = Config::getParam('roles', [])[USER_ROLE_APPS]['scopes']; $key = static::generateKey($projectId, $usage, $scopes); $project = new Document(['$id' => $projectId,]); @@ -29,7 +29,7 @@ class KeyTest extends TestCase $this->assertEquals($projectId, $decoded->getProjectId()); $this->assertEquals(API_KEY_DYNAMIC, $decoded->getType()); - $this->assertEquals(Auth::USER_ROLE_APPS, $decoded->getRole()); + $this->assertEquals(USER_ROLE_APPS, $decoded->getRole()); $this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes()); } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index 8ba0374093..536228b504 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -59,7 +59,7 @@ class MessagingChannelsTest extends TestCase 'confirm' => true, 'roles' => [ empty($index % 2) - ? Auth::USER_ROLE_ADMIN + ? USER_ROLE_ADMIN : 'member', ] ] @@ -294,7 +294,7 @@ class MessagingChannelsTest extends TestCase } $role = empty($index % 2) - ? Auth::USER_ROLE_ADMIN + ? USER_ROLE_ADMIN : 'member'; $permissions = [ From 477add30229c9c145805f341fec58ae25dcc94b6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 21:49:10 +0100 Subject: [PATCH 015/385] Formatting --- app/config/collections/common.php | 1 - app/config/console.php | 1 - app/config/roles.php | 1 - app/controllers/api/projects.php | 1 - src/Appwrite/Migration/Version/V16.php | 1 - src/Appwrite/Migration/Version/V17.php | 1 - src/Appwrite/Migration/Version/V20.php | 1 - src/Appwrite/Platform/Workers/Audits.php | 1 - src/Appwrite/Platform/Workers/Deletes.php | 1 - tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 1 - tests/unit/Auth/KeyTest.php | 1 - 11 files changed, 11 deletions(-) diff --git a/app/config/collections/common.php b/app/config/collections/common.php index 7e7da1c94d..00ef59968d 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1,6 +1,5 @@ Date: Mon, 17 Mar 2025 22:51:22 +0100 Subject: [PATCH 016/385] Improved errors --- app/controllers/general.php | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 3afe1d8a3d..b099031036 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -977,17 +977,29 @@ App::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { - Console::error('[Error] Timestamp: ' . date('c', time())); + $logLevel = $code >= 500 ? 'error' : 'warning'; + $logPrefix = $code >= 500 ? '[Error]' : '[Warning]'; + + Console::$logLevel($logPrefix . ' Timestamp: ' . date('c', time())); if ($route) { - Console::error('[Error] Method: ' . $route->getMethod()); - Console::error('[Error] URL: ' . $route->getPath()); + Console::$logLevel($logPrefix . ' Status Code: ' . $code); + Console::$logLevel($logPrefix . ' Method: ' . $route->getMethod()); + Console::$logLevel($logPrefix . ' URL: ' . $route->getPath()); + } + Console::$logLevel($logPrefix . ' Type: ' . get_class($error)); + Console::$logLevel($logPrefix . ' Message: ' . $message); + Console::$logLevel($logPrefix . ' File: ' . $file); + Console::$logLevel($logPrefix . ' Line: ' . $line); + Console::$logLevel($logPrefix . ' Trace:'); + foreach ($trace as $index => $entry) { + $file = $entry['file'] ?? 'unknown'; + $line = $entry['line'] ?? 0; + $function = $entry['function'] ?? 'unknown'; + $class = $entry['class'] ?? ''; + $type = $entry['type'] ?? ''; + Console::$logLevel(" #{$index} {$file}({$line}): {$class}{$type}{$function}()"); } - - Console::error('[Error] Type: ' . get_class($error)); - Console::error('[Error] Message: ' . $message); - Console::error('[Error] File: ' . $file); - Console::error('[Error] Line: ' . $line); } switch ($class) { From f537091eb23824e56aa5d93a044282572c542801 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 23:57:44 +0100 Subject: [PATCH 017/385] Fixed tests --- app/controllers/api/account.php | 9 ++++++--- app/controllers/general.php | 18 +++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 812b454cac..4159b2eee0 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -902,7 +902,7 @@ App::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - $userProofForPassword = ProofsPassword::createHash($profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); + $userProofForPassword = ProofsPassword::createHash($profile->getAttribute('hash', $proofForPassword->getHash()->getName()), $profile->getAttribute('hashOptions', $proofForPassword->getHash()->getOptions())); if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !$userProofForPassword->verify($password, $profile->getAttribute('password'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); @@ -1457,7 +1457,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $name = ''; $nameOAuth = $oauth2->getUserName($accessToken); - $userParam = \json_decode($request->getParam('user'), true); + $userParam = \json_decode($request->getParam('user', '{}'), true); // only valid for Apple OAuth2 which returns a user param in the request if (!empty($nameOAuth)) { $name = $nameOAuth; } elseif (is_array($userParam)) { @@ -2472,7 +2472,8 @@ App::post('/v1/account/tokens/phone') ->inject('queueForStatsUsage') ->inject('plan') ->inject('store') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store) { + ->inject('proofForPassword') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsPassword $proofForPassword) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -2510,6 +2511,8 @@ App::post('/v1/account/tokens/phone') 'status' => true, 'password' => null, 'passwordUpdate' => null, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'registration' => DateTime::now(), 'reset' => false, 'prefs' => new \stdClass(), diff --git a/app/controllers/general.php b/app/controllers/general.php index b099031036..77da6189c7 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -978,14 +978,13 @@ App::error() if (php_sapi_name() === 'cli') { $logLevel = $code >= 500 ? 'error' : 'warning'; - $logPrefix = $code >= 500 ? '[Error]' : '[Warning]'; + $logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]'; Console::$logLevel($logPrefix . ' Timestamp: ' . date('c', time())); if ($route) { Console::$logLevel($logPrefix . ' Status Code: ' . $code); - Console::$logLevel($logPrefix . ' Method: ' . $route->getMethod()); - Console::$logLevel($logPrefix . ' URL: ' . $route->getPath()); + Console::$logLevel($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath()); } Console::$logLevel($logPrefix . ' Type: ' . get_class($error)); Console::$logLevel($logPrefix . ' Message: ' . $message); @@ -993,13 +992,14 @@ App::error() Console::$logLevel($logPrefix . ' Line: ' . $line); Console::$logLevel($logPrefix . ' Trace:'); foreach ($trace as $index => $entry) { - $file = $entry['file'] ?? 'unknown'; - $line = $entry['line'] ?? 0; - $function = $entry['function'] ?? 'unknown'; - $class = $entry['class'] ?? ''; - $type = $entry['type'] ?? ''; - Console::$logLevel(" #{$index} {$file}({$line}): {$class}{$type}{$function}()"); + $traceFile = $entry['file'] ?? 'unknown'; + $traceLine = $entry['line'] ?? 0; + $traceFunction = $entry['function'] ?? 'unknown'; + $traceClass = $entry['class'] ?? ''; + $traceType = $entry['type'] ?? ''; + Console::$logLevel(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()"); } + Console::$logLevel(''); } switch ($class) { From 0180f72067939ce59c2489736be9253d12937b49 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 00:30:31 +0100 Subject: [PATCH 018/385] Removed unsed methods and tests --- src/Appwrite/Auth/Auth.php | 12 --- tests/unit/Auth/AuthTest.php | 137 +---------------------------------- 2 files changed, 1 insertion(+), 148 deletions(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 18b863b15b..d644483b74 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -231,16 +231,4 @@ class Auth return $roles; } - - /** - * Check if user is anonymous. - * - * @param Document $user - * @return bool - */ - public static function isAnonymousUser(Document $user): bool - { - return is_null($user->getAttribute('email')) - && is_null($user->getAttribute('phone')); - } } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index e8cf938ce9..02d433106f 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -28,142 +28,7 @@ class AuthTest extends TestCase $secret = 'secret'; $this->assertEquals(Auth::hash($secret), '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b'); } - - public function testPassword(): void - { - /* - General tests, using pre-defined hashes generated by online tools - */ - - // Bcrypt - Version Y - $plain = 'secret'; - $hash = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy'; - $generatedHash = Password::createHash('bcrypt')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); - - // Bcrypt - Version A - $plain = 'test123'; - $hash = '$2a$12$3f2ZaARQ1AmhtQWx2nmQpuXcWfTj1YV2/Hl54e8uKxIzJe3IfwLiu'; - $generatedHash = Password::createHash('bcrypt')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); - - // Bcrypt - Cost 5 - $plain = 'hello-world'; - $hash = '$2a$05$IjrtSz6SN7UJ6Sh3l.b5jODEvEG2LMJTPAHIaLWRvlWx7if3VMkFO'; - $generatedHash = Password::createHash('bcrypt')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); - - // Bcrypt - Cost 15 - $plain = 'super-secret-password'; - $hash = '$2a$15$DS0ZzbsFZYumH/E4Qj5oeOHnBcM3nCCsCA2m4Goigat/0iMVQC4Na'; - $generatedHash = Password::createHash('bcrypt')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); - - // MD5 - Short - $plain = 'appwrite'; - $hash = '144fa7eaa4904e8ee120651997f70dcc'; - $generatedHash = Password::createHash('md5')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); - - // MD5 - Long - $plain = 'AppwriteIsAwesomeBackendAsAServiceThatIsAlsoOpenSourced'; - $hash = '8410e96cf7ac64e0b84c3f8517a82616'; - $generatedHash = Password::createHash('md5')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); - - // PHPass - $plain = 'pass123'; - $hash = '$P$BVKPmJBZuLch27D4oiMRTEykGLQ9tX0'; - $generatedHash = Password::createHash('phpass')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); - - // SHA - $plain = 'developersAreAwesome!'; - $hash = '2455118438cb125354b89bb5888346e9bd23355462c40df393fab514bf2220b5a08e4e2d7b85d7327595a450d0ac965cc6661152a46a157c66d681bed20a4735'; - $generatedHash = Password::createHash('sha')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'sha')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'sha')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'sha')); - - // Argon2 - $plain = 'safe-argon-password'; - $hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8'; - $generatedHash = Password::createHash('argon2')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'argon2')); - - // Scrypt - $plain = 'some-scrypt-password'; - $hash = 'b448ad7ba88b653b5b56b8053a06806724932d0751988bc9cd0ef7ff059e8ba8a020e1913b7069a650d3f99a1559aba0221f2c277826919513a054e76e339028'; - $generatedHash = Password::createHash('scrypt')->setOptions([ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])->hash($plain); - - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); - $this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-wrong-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); - $this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 10, 'costParallel' => 2])); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); - - // ScryptModified tested are in provider-specific tests below - - /* - Provider-specific tests, ensuring functionality of specific use-cases - */ - - // Provider #1 (Database) - $plain = 'example-password'; - $hash = '$2a$10$3bIGRWUes86CICsuchGLj.e.BqdCdg2/1Ud9LvBhJr0j7Dze8PBdS'; - $generatedHash = Password::createHash('bcrypt')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); - - // Provider #2 (Blog) - $plain = 'your-password'; - $hash = '$P$BkiNDJTpAWXtpaMhEUhUdrv7M0I1g6.'; - $generatedHash = Password::createHash('phpass')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); - - // Provider #2 (Google) - $plain = 'users-password'; - $hash = 'EPKgfALpS9Tvgr/y1ki7ubY4AEGJeWL3teakrnmOacN4XGiyD00lkzEHgqCQ71wGxoi/zb7Y9a4orOtvMV3/Jw=='; - $salt = '56dFqW+kswqktw=='; - $saltSeparator = 'Bw=='; - $signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ=='; - - $options = [ 'salt' => $salt, 'saltSeparator' => $saltSeparator, 'signerKey' => $signerKey ]; - $generatedHash = Password::createHash('scryptMod')->hash($plain, $options); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scryptMod', $options)); - $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scryptMod', $options)); - $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scryptMod', $options)); - } - - public function testUnknownAlgo() - { - $this->expectExceptionMessage('Hashing algorithm \'md8\' is not supported.'); - - // Bcrypt - Cost 5 - $plain = 'whatIsMd8?!?'; - $generatedHash = Password::createHash('md8')->hash($plain); - $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8')); - } - + public function testTokenGenerator(): void { $this->assertEquals(\strlen(Auth::tokenGenerator()), 256); From 84cec0e32cd7aa5379849b01e2bb55d01f54800a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 10:18:29 +0100 Subject: [PATCH 019/385] format --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 1 - src/Appwrite/Auth/MFA/Type.php | 1 - src/Appwrite/Platform/Tasks/Install.php | 1 - tests/unit/Auth/AuthTest.php | 1 - 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8f7d615ca7..0a157c37fe 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -38,9 +38,9 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; +use Utopia\Auth\Proofs\Code as ProofsCode; use Utopia\Auth\Proofs\Password as ProofsPassword; use Utopia\Auth\Proofs\Token as ProofsToken; -use Utopia\Auth\Proofs\Code as ProofsCode; use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; @@ -3234,7 +3234,7 @@ App::post('/v1/account/recovery') ->inject('locale') ->inject('queueForMails') ->inject('queueForEvents') - ->inject('proofForToken') + ->inject('proofForToken') ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 7421e1526a..bc02a9dd85 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1,7 +1,6 @@ Date: Tue, 18 Mar 2025 10:24:42 +0100 Subject: [PATCH 020/385] Fixed syntax error --- app/init/resources.php | 4 +++- src/Appwrite/Auth/Auth.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/init/resources.php b/app/init/resources.php index 41c099ce05..41e70b4e34 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -870,7 +870,9 @@ App::setResource('proofForToken', function (): Token { }); App::setResource('proofForTokenCode', function (): Token { - return new Token()->setLength(6); + $token = new Token(); + $token->setLength(6); + return $token; }); App::setResource('proofForCode', function (): Code { diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index a6c6e87d4e..a838a9ce75 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -12,11 +12,18 @@ class Auth { /** * @var string + * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. */ public static $cookieNamePreview = 'a_jwt_console'; /** * Token type to session provider mapping. + * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. + * @param int $type + * + * @return string */ public static function getSessionProviderByTokenType(int $type): string { @@ -41,6 +48,7 @@ class Auth * * One-way encryption * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param $string * * @return string @@ -55,6 +63,7 @@ class Auth * * Generate random password string * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param int $length Length of returned token * * @return string @@ -74,6 +83,7 @@ class Auth /** * Verify token and check that its not expired. * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $tokens * @param int $type Type of token to verify, if null will verify any type * @param string $secret @@ -101,6 +111,7 @@ class Auth /** * Verify session and check that its not expired. * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $sessions * @param string $secret * @@ -125,6 +136,7 @@ class Auth /** * Is Privileged User? * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $roles * * @return bool @@ -145,6 +157,7 @@ class Auth /** * Is App User? * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $roles * * @return bool @@ -161,6 +174,7 @@ class Auth /** * Returns all roles for a user. * + * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param Document $user * @return array */ From afb40218d73ccc53e922fd64ab25dd029b699053 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 16:12:13 +0100 Subject: [PATCH 021/385] Fixed tests --- app/controllers/api/account.php | 92 ++++++++++--------- app/controllers/api/teams.php | 2 +- app/controllers/general.php | 2 +- app/init/resources.php | 36 +++++--- app/realtime.php | 4 +- src/Appwrite/Auth/Auth.php | 10 +- .../Functions/Http/Executions/Create.php | 7 +- .../Account/AccountConsoleClientTest.php | 2 +- .../Account/AccountCustomClientTest.php | 2 +- .../Account/AccountCustomServerTest.php | 2 +- tests/unit/Auth/AuthTest.php | 6 -- 11 files changed, 91 insertions(+), 74 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 0a157c37fe..703ba764ec 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -170,7 +170,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res throw new Exception(Exception::USER_INVALID_TOKEN); } - $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret); + $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForToken); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -539,14 +539,15 @@ App::get('/v1/account/sessions') ->inject('user') ->inject('locale') ->inject('store') - ->action(function (Response $response, Document $user, Locale $locale, Store $store) { + ->inject('proofForToken') + ->action(function (Response $response, Document $user, Locale $locale, Store $store, ProofsToken $proofForToken) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', '')); + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); foreach ($sessions as $key => $session) {/** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -593,7 +594,8 @@ App::delete('/v1/account/sessions') ->inject('queueForEvents') ->inject('queueForDeletes') ->inject('store') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store) { + ->inject('proofForToken') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) { $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); @@ -609,7 +611,7 @@ App::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { + if ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', ''))) { $session->setAttribute('current', true); // If current session delete the cookies too @@ -659,7 +661,8 @@ App::get('/v1/account/sessions/:sessionId') ->inject('user') ->inject('locale') ->inject('store') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Store $store) { + ->inject('proofForToken') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Store $store, ProofsToken $proofForToken) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ -667,7 +670,7 @@ App::get('/v1/account/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ @@ -675,7 +678,7 @@ App::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', '')))) + ->setAttribute('current', ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', '')))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '') ; @@ -718,11 +721,12 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('queueForEvents') ->inject('queueForDeletes') ->inject('store') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store) { + ->inject('proofForToken') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -741,7 +745,7 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too + if ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', ''))) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); @@ -803,10 +807,11 @@ App::patch('/v1/account/sessions/:sessionId') ->inject('project') ->inject('queueForEvents') ->inject('store') - ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store) { + ->inject('proofForToken') + ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store, ProofsToken $proofForToken) { $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')) + ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -934,7 +939,7 @@ App::post('/v1/account/sessions/email') 'userInternalId' => $user->getInternalId(), 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => $email, - 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak + 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => ['password'], @@ -1193,7 +1198,7 @@ App::post('/v1/account/sessions/token') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->inject('proofForTokenCode') + ->inject('proofForToken') ->action($createSession); App::get('/v1/account/sessions/oauth2/:provider') @@ -1498,7 +1503,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', '')); + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); if ($current) { // Delete current session of new one. $currentDocument = $dbForProject->getDocument('sessions', $current); @@ -2164,8 +2169,8 @@ App::post('/v1/account/tokens/email') ->inject('queueForEvents') ->inject('queueForMails') ->inject('proofForPassword') - ->inject('proofForTokenCode') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsToken $proofForTokenCode) { + ->inject('proofForCode') + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2230,7 +2235,7 @@ App::post('/v1/account/tokens/email') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $tokenSecret = $proofForTokenCode->generate(); + $tokenSecret = $proofForCode->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); $token = new Document([ @@ -2238,7 +2243,7 @@ App::post('/v1/account/tokens/email') 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'type' => TOKEN_TYPE_EMAIL, - 'secret' => Auth::hash($tokenSecret), // One way hash encryption to protect DB leak + 'secret' => $proofForCode->hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2402,7 +2407,7 @@ App::put('/v1/account/sessions/magic-url') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->inject('proofForTokenCode') + ->inject('proofForToken') ->action($createSession); App::put('/v1/account/sessions/phone') @@ -2441,7 +2446,7 @@ App::put('/v1/account/sessions/phone') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->inject('proofForTokenCode') + ->inject('proofForToken') ->action($createSession); App::post('/v1/account/tokens/phone') @@ -2689,19 +2694,15 @@ App::post('/v1/account/jwts') ->inject('response') ->inject('user') ->inject('store') - ->action(function (Response $response, Document $user, Store $store) { + ->inject('proofForToken') + ->action(function (Response $response, Document $user, Store $store, ProofsToken $proofForToken) { $sessions = $user->getAttribute('sessions', []); - $current = new Document(); - foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too - $current = $session; - } - } + $sessionId = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); - if ($current->isEmpty()) { + if (!$sessionId) { throw new Exception(Exception::USER_SESSION_NOT_FOUND); } @@ -2711,7 +2712,7 @@ App::post('/v1/account/jwts') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic(new Document(['jwt' => $jwt->encode([ 'userId' => $user->getId(), - 'sessionId' => $current->getId(), + 'sessionId' => $sessionId, ])]), Response::MODEL_JWT); }); @@ -3421,7 +3422,8 @@ App::put('/v1/account/recovery') ->inject('queueForEvents') ->inject('hooks') ->inject('proofForPassword') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword) { + ->inject('proofForToken') + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3429,7 +3431,7 @@ App::put('/v1/account/recovery') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_RECOVERY, $secret); + $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_RECOVERY, $secret, $proofForToken); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3680,7 +3682,8 @@ App::put('/v1/account/verification') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('proofForToken') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) { $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -3689,7 +3692,7 @@ App::put('/v1/account/verification') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_VERIFICATION, $secret); + $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_VERIFICATION, $secret, $proofForToken); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3751,8 +3754,8 @@ App::post('/v1/account/verification/phone') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->inject('proofForToken') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsToken $proofForToken) { + ->inject('proofForCode') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3781,7 +3784,7 @@ App::post('/v1/account/verification/phone') } } - $secret ??= $proofForToken->generate(); + $secret ??= $proofForCode->generate(); $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); $verification = new Document([ @@ -3789,7 +3792,7 @@ App::post('/v1/account/verification/phone') 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'type' => TOKEN_TYPE_PHONE, - 'secret' => $proofForToken->hash($secret), + 'secret' => $proofForCode->hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -3904,7 +3907,8 @@ App::put('/v1/account/verification/phone') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('proofForCode') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) { $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -3912,7 +3916,7 @@ App::put('/v1/account/verification/phone') throw new Exception(Exception::USER_NOT_FOUND); } - $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), TOKEN_TYPE_PHONE, $secret); + $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), TOKEN_TYPE_PHONE, $secret, $proofForCode); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -4389,6 +4393,7 @@ App::post('/v1/account/mfa/challenge') ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsToken $proofForToken, ProofsCode $proofForCode) { $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); + $code = $proofForCode->generate(); $challenge = new Document([ 'userId' => $user->getId(), @@ -4692,7 +4697,8 @@ App::post('/v1/account/targets/push') ->inject('response') ->inject('dbForProject') ->inject('store') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Store $store) { + ->inject('proofForToken') + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); @@ -4708,7 +4714,7 @@ App::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken); $session = $dbForProject->getDocument('sessions', $sessionId); try { diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 72838fe001..0e80cbb398 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -624,7 +624,7 @@ App::post('/v1/teams/:teamId/memberships') Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); } elseif ($membership->getAttribute('confirm') === false) { - $membership->setAttribute('secret', Auth::hash($secret)); + $membership->setAttribute('secret', $proofForToken->hash($secret)); $membership->setAttribute('invited', DateTime::now()); if ($isPrivilegedUser || $isAppUser) { diff --git a/app/controllers/general.php b/app/controllers/general.php index 77da6189c7..8926c5e6ce 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -977,7 +977,7 @@ App::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { - $logLevel = $code >= 500 ? 'error' : 'warning'; + $logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning'; $logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]'; Console::$logLevel($logPrefix . ' Timestamp: ' . date('c', time())); diff --git a/app/init/resources.php b/app/init/resources.php index 41e70b4e34..84d2a65d3a 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -23,6 +23,8 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Hashes\Argon2; +use Utopia\Auth\Hashes\Sha; use Utopia\Auth\Proofs\Code; use Utopia\Auth\Proofs\Password; use Utopia\Auth\Proofs\Token; @@ -168,7 +170,7 @@ App::setResource('clients', function ($request, $console, $project) { return \array_unique($clients); }, ['request', 'console', 'project']); -App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, Store $store) { +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, Store $store, Token $proofForToken) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ @@ -250,7 +252,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons if ( $user->isEmpty() // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')) + || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) ) { // Validate user has valid login token $user = new Document([]); } @@ -291,7 +293,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $dbForPlatform->setMetadata('user', $user->getId()); return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store']); +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']); App::setResource('project', function ($dbForPlatform, $request, $console) { /** @var Appwrite\Utopia\Request $request */ @@ -309,13 +311,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) { return $project; }, ['dbForPlatform', 'request', 'console']); -App::setResource('session', function (Document $user, Store $store) { +App::setResource('session', function (Document $user, Store $store, Token $proofForToken) { if ($user->isEmpty()) { return; } $sessions = $user->getAttribute('sessions', []); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', '')); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken); if (!$sessionId) { return; @@ -328,7 +330,7 @@ App::setResource('session', function (Document $user, Store $store) { } return; -}, ['user', 'store']); +}, ['user', 'store', 'proofForToken']); App::setResource('console', function () { return new Document(Config::getParam('console')); @@ -862,19 +864,27 @@ App::setResource('store', function (): Store { }); App::setResource('proofForPassword', function (): Password { - return new Password(); + $hash = new Argon2(); + $hash + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); + + $password = new Password(); + $password + ->setHash($hash); + + return $password; }); App::setResource('proofForToken', function (): Token { - return new Token(); -}); - -App::setResource('proofForTokenCode', function (): Token { $token = new Token(); - $token->setLength(6); + $token->setHash(new Sha()); return $token; }); App::setResource('proofForCode', function (): Code { - return new Code(); + $code = new Code(); + $code->setHash(new Sha()); + return $code; }); diff --git a/app/realtime.php b/app/realtime.php index d65a2559b5..6d10fd7674 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -15,6 +15,7 @@ use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; @@ -654,10 +655,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $store->decode($message['data']['session']); $user = $database->getDocument('users', $store->getProperty('id', '')); + $proofForToken = new Token(); if ( empty($user->getId()) // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', '')) // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token ) { // cookie not valid throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index a838a9ce75..86d1e197bf 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -2,6 +2,8 @@ namespace Appwrite\Auth; +use Utopia\Auth\Proof; +use Utopia\Auth\Proofs\Token; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; @@ -90,7 +92,7 @@ class Auth * * @return false|Document */ - public static function tokenVerify(array $tokens, int $type = null, string $secret): false|Document + public static function tokenVerify(array $tokens, int $type = null, string $secret, Proof $proofForToken): false|Document { foreach ($tokens as $token) { if ( @@ -98,7 +100,7 @@ class Auth $token->isSet('expire') && $token->isSet('type') && ($type === null || $token->getAttribute('type') === $type) && - $token->getAttribute('secret') === self::hash($secret) && + $proofForToken->verify($secret, $token->getAttribute('secret')) && DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now()) ) { return $token; @@ -117,13 +119,13 @@ class Auth * * @return bool|string */ - public static function sessionVerify(array $sessions, string $secret) + public static function sessionVerify(array $sessions, string $secret, Token $proofForToken) { foreach ($sessions as $session) { if ( $session->isSet('secret') && $session->isSet('provider') && - $session->getAttribute('secret') === self::hash($secret) && + $proofForToken->verify($secret, $session->getAttribute('secret')) && DateTime::formatTz(DateTime::format(new \DateTime($session->getAttribute('expire')))) >= DateTime::formatTz(DateTime::now()) ) { return $session->getId(); diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 3a7030742e..af24e7c9f7 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -19,6 +19,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Executor\Executor; use MaxMind\Db\Reader; +use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -95,6 +96,7 @@ class Create extends Base ->inject('queueForFunctions') ->inject('geodb') ->inject('store') + ->inject('proofForToken') ->callback([$this, 'action']); } @@ -116,7 +118,8 @@ class Create extends Base StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, - Store $store + Store $store, + Token $proofForToken ) { $async = \strval($async) === 'true' || \strval($async) === '1'; @@ -193,7 +196,7 @@ class Create extends Base foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash($store->getProperty('secret', ''))) { // If current session delete the cookies too + if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too $current = $session; } } diff --git a/tests/e2e/Services/Account/AccountConsoleClientTest.php b/tests/e2e/Services/Account/AccountConsoleClientTest.php index 1df9ef6c18..51de5731bd 100644 --- a/tests/e2e/Services/Account/AccountConsoleClientTest.php +++ b/tests/e2e/Services/Account/AccountConsoleClientTest.php @@ -45,7 +45,6 @@ class AccountConsoleClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 201); $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; - // create team $team = $this->client->call(Client::METHOD_POST, '/teams', [ 'origin' => 'http://localhost', @@ -56,6 +55,7 @@ class AccountConsoleClientTest extends Scope 'teamId' => 'unique()', 'name' => 'myteam' ]); + $this->assertEquals($team['headers']['status-code'], 201); $teamId = $team['body']['$id']; diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index daa5bcbff8..683988f10e 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2535,7 +2535,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']); $this->assertStringNotContainsStringIgnoringCase('security phrase', $lastEmail['text']); - $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64); + $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); $expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0); diff --git a/tests/e2e/Services/Account/AccountCustomServerTest.php b/tests/e2e/Services/Account/AccountCustomServerTest.php index eb72a99913..e0a52c4007 100644 --- a/tests/e2e/Services/Account/AccountCustomServerTest.php +++ b/tests/e2e/Services/Account/AccountCustomServerTest.php @@ -218,7 +218,7 @@ class AccountCustomServerTest extends Scope $this->assertEquals($email, $lastEmail['to'][0]['address']); $this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']); - $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64); + $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); $expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0); diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index e12b5c36a6..84186ea222 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -22,12 +22,6 @@ class AuthTest extends TestCase Authorization::setRole(Role::any()->toString()); } - public function testHash(): void - { - $secret = 'secret'; - $this->assertEquals(Auth::hash($secret), '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b'); - } - public function testSessionVerify(): void { $expireTime1 = 60 * 60 * 24; From 4d5961c3ab9ed29aed2f30e4931fb4f13d9275ec Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 16:20:37 +0100 Subject: [PATCH 022/385] Fixed test --- app/controllers/api/users.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index bc02a9dd85..d4e8c9cb48 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2113,7 +2113,9 @@ App::post('/v1/users/:userId/tokens') throw new Exception(Exception::USER_NOT_FOUND); } - $secret = $proofForToken->generate(); + $secret = $proofForToken + ->setLength($length) + ->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire)); $token = new Document([ From 24300cd3bded892cd09ea1ad7c7c441486c9c72b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 17:22:58 +0100 Subject: [PATCH 023/385] tests --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3585dbb68..4af1370403 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> [Get started with Appwrite](https://apwr.dev/appcloud) +> [Get started with Appwrite](https://apwr.dev/appcloud)

From 55e560bb413b40f10c7a36c77d752b72771ad395 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 19:08:58 +0100 Subject: [PATCH 024/385] Fixed unit tests --- tests/unit/Auth/AuthTest.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 84186ea222..22a84b4c3a 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -10,7 +10,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Roles; - +use Utopia\Auth\Proofs\Token; class AuthTest extends TestCase { /** @@ -64,10 +64,12 @@ class AuthTest extends TestCase ]), ]; - $this->assertEquals(Auth::sessionVerify($tokens1, $secret), 'token1'); - $this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret'), false); - $this->assertEquals(Auth::sessionVerify($tokens2, $secret), false); - $this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret'), false); + $proofForToken = new Token(); + + $this->assertEquals(Auth::sessionVerify($tokens1, $secret, $proofForToken), 'token1'); + $this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret', $proofForToken), false); + $this->assertEquals(Auth::sessionVerify($tokens2, $secret, $proofForToken), false); + $this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret', $proofForToken), false); } public function testTokenVerify(): void From 8cb85cafbd0b4d739e59faf5f56988fc64b2b760 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 20:24:38 +0100 Subject: [PATCH 025/385] Fixes realtime tests --- app/realtime.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/realtime.php b/app/realtime.php index 6d10fd7674..95dabaafba 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -15,6 +15,7 @@ use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Hashes\Sha; use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\Cache\Adapter\Sharding; @@ -652,10 +653,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } $store = new Store(); + $store->decode($message['data']['session']); $user = $database->getDocument('users', $store->getProperty('id', '')); + + /** + * TODO: + * Moving forward, we should try to use our dependency injection container + * to inject the proof for token. + * This way we will have one source of truth for the proof for token. + */ $proofForToken = new Token(); + $proofForToken->setHash(new Sha()); if ( empty($user->getId()) // Check a document has been found in the DB From fed6579491c401f5c6f230679b374fc92a49de63 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 21:52:41 +0100 Subject: [PATCH 026/385] Update teams tests --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 7e82876806..3cd44d69c2 100644 --- a/composer.lock +++ b/composer.lock @@ -3512,12 +3512,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "ed49b9e481030ba5e589140b41a9f4be1486310f" + "reference": "dfdf614644237700e41935b51da7e39f6848a6e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/ed49b9e481030ba5e589140b41a9f4be1486310f", - "reference": "ed49b9e481030ba5e589140b41a9f4be1486310f", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/dfdf614644237700e41935b51da7e39f6848a6e7", + "reference": "dfdf614644237700e41935b51da7e39f6848a6e7", "shasum": "" }, "require": { @@ -3559,7 +3559,7 @@ "issues": "https://github.com/utopia-php/auth/issues", "source": "https://github.com/utopia-php/auth/tree/dev" }, - "time": "2025-03-17T19:57:57+00:00" + "time": "2025-03-18T19:34:43+00:00" }, { "name": "utopia-php/cache", From b2b20c48b36f90168aafafdb8814ff810e16b71f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 22:12:03 +0100 Subject: [PATCH 027/385] Fixed tests --- app/controllers/api/account.php | 2 +- app/realtime.php | 4 ++-- src/Appwrite/Migration/Migration.php | 1 + src/Appwrite/Migration/Version/V23.php | 29 ++++++++++++++++++++++++++ tests/unit/Auth/AuthTest.php | 25 +++++++++++----------- tests/unit/Migration/MigrationTest.php | 2 +- 6 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 src/Appwrite/Migration/Version/V23.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 703ba764ec..189982e45a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -4393,7 +4393,7 @@ App::post('/v1/account/mfa/challenge') ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsToken $proofForToken, ProofsCode $proofForCode) { $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); - + $code = $proofForCode->generate(); $challenge = new Document([ 'userId' => $user->getId(), diff --git a/app/realtime.php b/app/realtime.php index 95dabaafba..d65a7cdb69 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -653,11 +653,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } $store = new Store(); - + $store->decode($message['data']['session']); $user = $database->getDocument('users', $store->getProperty('id', '')); - + /** * TODO: * Moving forward, we should try to use our dependency injection container diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 56016f1057..17e93f43f5 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -93,6 +93,7 @@ abstract class Migration '1.6.0' => 'V21', '1.6.1' => 'V21', '1.6.2' => 'V22', + '1.7.0' => 'V23', ]; /** diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php new file mode 100644 index 0000000000..f6f830436d --- /dev/null +++ b/src/Appwrite/Migration/Version/V23.php @@ -0,0 +1,29 @@ +hash($secret); $tokens1 = [ new Document([ '$id' => ID::custom('token1'), @@ -64,8 +66,6 @@ class AuthTest extends TestCase ]), ]; - $proofForToken = new Token(); - $this->assertEquals(Auth::sessionVerify($tokens1, $secret, $proofForToken), 'token1'); $this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret', $proofForToken), false); $this->assertEquals(Auth::sessionVerify($tokens2, $secret, $proofForToken), false); @@ -74,8 +74,9 @@ class AuthTest extends TestCase public function testTokenVerify(): void { + $proofForToken = new Token(); $secret = 'secret1'; - $hash = Auth::hash($secret); + $hash = $proofForToken->hash($secret); $tokens1 = [ new Document([ '$id' => ID::custom('token1'), @@ -121,13 +122,13 @@ class AuthTest extends TestCase ]), ]; - $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, $secret), $tokens1[0]); - $this->assertEquals(Auth::tokenVerify($tokens1, null, $secret), $tokens1[0]); - $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, 'false-secret'), false); - $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, $secret), false); - $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, 'false-secret'), false); - $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, $secret), false); - $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), $tokens1[0]); + $this->assertEquals(Auth::tokenVerify($tokens1, null, $secret, $proofForToken), $tokens1[0]); + $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); + $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false); + $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); + $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false); + $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); } public function testIsPrivilegedUser(): void diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php index 536278d55b..8c619b76c2 100644 --- a/tests/unit/Migration/MigrationTest.php +++ b/tests/unit/Migration/MigrationTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use ReflectionMethod; use Utopia\Database\Document; -abstract class MigrationTest extends TestCase +class MigrationTest extends TestCase { /** * @var Migration From 38cb95e94018d97de1d811d28f5bd029dac3bb63 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 22:28:19 +0100 Subject: [PATCH 028/385] Fixed tests --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 0e80cbb398..489e801af3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1153,7 +1153,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH); } - if ($proofForToken->verify($membership->getAttribute('secret'), $secret)) { + if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) { throw new Exception(Exception::TEAM_INVALID_SECRET); } From a7d7e39dfd96344c93c75bc51f1f84922babed88 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 18 Mar 2025 22:47:17 +0100 Subject: [PATCH 029/385] Fixed tests --- app/controllers/api/account.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 189982e45a..d029eff4f0 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -611,7 +611,7 @@ App::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', ''))) { + if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { $session->setAttribute('current', true); // If current session delete the cookies too @@ -678,7 +678,7 @@ App::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', '')))) + ->setAttribute('current', ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret')))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '') ; @@ -745,7 +745,7 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($proofForToken->verify($session->getAttribute('secret'), $store->getProperty('secret', ''))) { // If current session delete the cookies too + if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); From 3d967e695f641a122459048b5b746e31aeeb2073 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 19 Mar 2025 07:57:28 +0100 Subject: [PATCH 030/385] Updated auth lib --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 3cd44d69c2..3751d5c3a1 100644 --- a/composer.lock +++ b/composer.lock @@ -3512,12 +3512,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "dfdf614644237700e41935b51da7e39f6848a6e7" + "reference": "19fb580de44fac5928f9c0211fd0fdfd5022efdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/dfdf614644237700e41935b51da7e39f6848a6e7", - "reference": "dfdf614644237700e41935b51da7e39f6848a6e7", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/19fb580de44fac5928f9c0211fd0fdfd5022efdb", + "reference": "19fb580de44fac5928f9c0211fd0fdfd5022efdb", "shasum": "" }, "require": { @@ -3559,7 +3559,7 @@ "issues": "https://github.com/utopia-php/auth/issues", "source": "https://github.com/utopia-php/auth/tree/dev" }, - "time": "2025-03-18T19:34:43+00:00" + "time": "2025-03-19T06:47:02+00:00" }, { "name": "utopia-php/cache", From 8c9123beaa7919f6466ecbc66e0ca26ced91c5fe Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 19 Mar 2025 13:54:32 +0100 Subject: [PATCH 031/385] Fixed tests --- app/controllers/api/account.php | 10 +++++++--- app/controllers/api/users.php | 4 +++- src/Appwrite/Auth/Validator/PasswordHistory.php | 14 +++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d029eff4f0..51a1c4f101 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -383,7 +383,7 @@ App::post('/v1/account') 'emailVerification' => false, 'status' => true, 'password' => $hash, - 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], + 'passwordHistory' => $passwordHistory > 0 ? [$hash] : [], 'passwordUpdate' => DateTime::now(), 'hash' => $proof->getHash()->getName(), 'hashOptions' => $proof->getHash()->getOptions(), @@ -2894,9 +2894,11 @@ App::patch('/v1/account/password') $newPassword = $proofForPassword->hash($password); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; + $hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); $history = $user->getAttribute('passwordHistory', []); + if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions')); + $validator = new PasswordHistory($history, $hash); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } @@ -3441,10 +3443,12 @@ App::put('/v1/account/recovery') $newPassword = $proofForPassword->hash($password); + $hash = ProofsPassword::createHash($profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $profile->getAttribute('passwordHistory', []); + if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); + $validator = new PasswordHistory($history, $hash); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d4e8c9cb48..65a35d616a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1319,10 +1319,12 @@ App::patch('/v1/users/:userId/password') $newPassword = $hasher->hash($password); + $hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $user->getAttribute('passwordHistory', []); + if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions')); + $validator = new PasswordHistory($history, $hash); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } diff --git a/src/Appwrite/Auth/Validator/PasswordHistory.php b/src/Appwrite/Auth/Validator/PasswordHistory.php index 7677deafc0..9b40b6a794 100644 --- a/src/Appwrite/Auth/Validator/PasswordHistory.php +++ b/src/Appwrite/Auth/Validator/PasswordHistory.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Auth\Proofs\Password as ProofsPassword; +use Utopia\Auth\Hash; /** * Password. @@ -12,16 +12,14 @@ use Utopia\Auth\Proofs\Password as ProofsPassword; class PasswordHistory extends Password { protected array $history; - protected string $algo; - protected array $algoOptions; + protected Hash $hash; - public function __construct(array $history, string $algo, array $algoOptions = []) + public function __construct(array $history, Hash $hash) { parent::__construct(); $this->history = $history; - $this->algo = $algo; - $this->algoOptions = $algoOptions; + $this->hash = $hash; } /** @@ -45,10 +43,8 @@ class PasswordHistory extends Password */ public function isValid($value): bool { - $proofForPassword = ProofsPassword::createHash($this->algo, $this->algoOptions); - foreach ($this->history as $hash) { - if (!empty($hash) && $proofForPassword->verify($value, $hash)) { + if (!empty($hash) && $this->hash->verify($value, $hash)) { return false; } } From d6bd72cfd32635d37ff144d54ee079bbcda72ad5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 19 Mar 2025 14:10:56 +0100 Subject: [PATCH 032/385] formatting --- app/controllers/api/account.php | 2 +- app/controllers/api/users.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 51a1c4f101..74ad74b3db 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3446,7 +3446,7 @@ App::put('/v1/account/recovery') $hash = ProofsPassword::createHash($profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $profile->getAttribute('passwordHistory', []); - + if ($historyLimit > 0) { $validator = new PasswordHistory($history, $hash); if (!$validator->isValid($password)) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 65a35d616a..0ab7f1a9d7 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1322,7 +1322,7 @@ App::patch('/v1/users/:userId/password') $hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $user->getAttribute('passwordHistory', []); - + if ($historyLimit > 0) { $validator = new PasswordHistory($history, $hash); if (!$validator->isValid($password)) { From 17c555ab6c7f289046878f3582cddbb8c549c0ab Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 28 Mar 2025 02:13:17 +0100 Subject: [PATCH 033/385] Expanded the identities collection --- app/config/collections/common.php | 21 ++++ app/terminal.php | 194 ++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 app/terminal.php diff --git a/app/config/collections/common.php b/app/config/collections/common.php index 00ef59968d..14c3a7ea29 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1100,6 +1100,27 @@ return [ 'array' => false, 'filters' => ['json', 'encrypt'], ], + [ + '$id' => ID::custom('scopes'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('expire'), + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'required' => true, + 'signed' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], ], 'indexes' => [ [ diff --git a/app/terminal.php b/app/terminal.php new file mode 100644 index 0000000000..1d2f2f0b24 --- /dev/null +++ b/app/terminal.php @@ -0,0 +1,194 @@ +column('userId', Table::TYPE_STRING, 64); +$runtimes->column('runtimeHost', Table::TYPE_STRING, 255); +$runtimes->column('runtimePort', Table::TYPE_INT); +$runtimes->create(); + +// Store WebSocket client connections +$clients = []; + +const MAX_PACKAGE_LENGTH = 64000; +const MAX_RUNTIME_CONNECTIONS = 4096; + +$adapter = new Adapter\Swoole(port: System::getEnv('PORT', 80)); +$adapter + ->setPackageMaxLength(MAX_PACKAGE_LENGTH) + ->setWorkerNumber($workerNumber); + +$server = new Server($adapter); + +$server->onStart(function () use ($workerNumber) { + Console::success('Terminal WebSocket Proxy started successfully'); + Console::info('Listening on port: ' . System::getEnv('PORT', 80)); + Console::info('Worker processes: ' . $workerNumber); + Console::info('Max package length: ' . (MAX_PACKAGE_LENGTH / 1000) . 'KB'); + Console::info('Max runtime connections: ' . MAX_RUNTIME_CONNECTIONS); +}); + +$server->onOpen(function (int $connection, $request) use ($server, $runtimes, &$clients) { + try { + Console::info("New connection: {$connection}"); + + // Extract JWT from request + $token = $request->header['authorization'] ?? ''; + if (empty($token)) { + throw new Exception('Missing authentication token', 401); + } + + // Verify JWT and extract user information + $jwt = str_replace('Bearer ', '', $token); + $key = System::getEnv('_APP_OPENSSL_KEY_V1', ''); + $jwt = new JWT($key, 'HS256', 900, 0); + + try { + $payload = $jwt->decode($token); + $userId = $payload['userId'] ?? ''; + $sessionId = $payload['sessionId'] ?? ''; + + if (empty($userId) || empty($sessionId)) { + throw new Exception('Invalid JWT payload', 401); + } + } catch (\Exception $e) { + throw new Exception('Invalid JWT token', 401); + } + + // Get runtime details for user (this could come from your database/cache) + $runtimeHost = "runtime-{$userId}.internal"; // Example hostname + $runtimePort = 9000; + + // Create WebSocket connection to runtime + go(function () use ($server, $connection, &$clients, $runtimeHost, $runtimePort, $userId) { + try { + // $wsClient = new Client("ws://{$runtimeHost}:{$runtimePort}/", [ + // 'timeout' => 0, // Disable timeout for long-running connections + // 'filter' => ['text', 'binary', 'close'] // Only process these frame types + // ]); + + + $wsClient = new Client( + "ws://appwrite-traefik/v1/realtime", + [ + "headers" => [], + "timeout" => 30, + ] + ); + + // Store client connection + $clients[$connection] = [ + 'client' => $wsClient, + 'userId' => $userId + ]; + + // Forward messages from runtime back to client + while (true) { + try { + $message = $wsClient->receive(); + if ($message === null) { + // Connection closed normally + break; + } + $server->send([$connection], $message); + + // Yield to allow other coroutines to run + Swoole\Coroutine::yield(); + + } catch (\WebSocket\ConnectionException $e) { + Console::error("Runtime connection error for user {$userId}: " . $e->getMessage()); + break; + } + } + + // Cleanup on disconnect + $wsClient->close(); + unset($clients[$connection]); + $server->close($connection, CLOSE_NORMAL); + } catch (\WebSocket\ConnectionException $e) { + Console::error("Failed to connect to runtime for user {$userId}: " . $e->getMessage()); + $server->close($connection, CLOSE_SERVER_ERROR); + return; + } + }); + + // Send successful connection message + $server->send([$connection], json_encode([ + 'type' => 'connected', + 'data' => [ + 'userId' => $userId, + 'timestamp' => time() + ] + ])); + + } catch (Throwable $th) { + Console::error('Connection error: ' . $th->getMessage()); + + $server->send([$connection], json_encode([ + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] + ])); + + $server->close($connection, CLOSE_POLICY_VIOLATION); + } +}); + +$server->onMessage(function (int $connection, string $message) use ($server, &$clients) { + try { + if (!isset($clients[$connection])) { + throw new Exception('Client not connected to runtime', 1008); + } + + $wsClient = $clients[$connection]['client']; + try { + // Forward message to runtime + $wsClient->send($message); + } catch (\WebSocket\ConnectionException $e) { + throw new Exception('Runtime connection lost: ' . $e->getMessage(), 1008); + } + + } catch (Throwable $th) { + $server->send([$connection], json_encode([ + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] + ])); + + if ($th->getCode() === 1008) { + $server->close($connection, CLOSE_POLICY_VIOLATION); + } + } +}); + +$server->onClose(function (int $connection) use (&$clients) { + if (isset($clients[$connection])) { + $userId = $clients[$connection]['userId']; + $clients[$connection]['client']->close(); + unset($clients[$connection]); + Console::info("Closed connection for user {$userId}"); + } +}); + +$server->start(); \ No newline at end of file From 57edb4a38554cd82de79c81e8605120be653c541 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 28 Mar 2025 08:17:47 +0100 Subject: [PATCH 034/385] Removed leftovers --- README.md | 2 +- app/terminal.php | 194 --------------- src/Appwrite/Auth/Hash/Argon2.php | 47 ---- src/Appwrite/Auth/Hash/Bcrypt.php | 46 ---- src/Appwrite/Auth/Hash/Md5.php | 44 ---- src/Appwrite/Auth/Hash/Phpass.php | 290 ---------------------- src/Appwrite/Auth/Hash/Scrypt.php | 51 ---- src/Appwrite/Auth/Hash/Scryptmodified.php | 80 ------ src/Appwrite/Auth/Hash/Sha.php | 50 ---- 9 files changed, 1 insertion(+), 803 deletions(-) delete mode 100644 app/terminal.php delete mode 100644 src/Appwrite/Auth/Hash/Argon2.php delete mode 100644 src/Appwrite/Auth/Hash/Bcrypt.php delete mode 100644 src/Appwrite/Auth/Hash/Md5.php delete mode 100644 src/Appwrite/Auth/Hash/Phpass.php delete mode 100644 src/Appwrite/Auth/Hash/Scrypt.php delete mode 100644 src/Appwrite/Auth/Hash/Scryptmodified.php delete mode 100644 src/Appwrite/Auth/Hash/Sha.php diff --git a/README.md b/README.md index 4af1370403..c3585dbb68 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> [Get started with Appwrite](https://apwr.dev/appcloud) +> [Get started with Appwrite](https://apwr.dev/appcloud)

diff --git a/app/terminal.php b/app/terminal.php deleted file mode 100644 index 1d2f2f0b24..0000000000 --- a/app/terminal.php +++ /dev/null @@ -1,194 +0,0 @@ -column('userId', Table::TYPE_STRING, 64); -$runtimes->column('runtimeHost', Table::TYPE_STRING, 255); -$runtimes->column('runtimePort', Table::TYPE_INT); -$runtimes->create(); - -// Store WebSocket client connections -$clients = []; - -const MAX_PACKAGE_LENGTH = 64000; -const MAX_RUNTIME_CONNECTIONS = 4096; - -$adapter = new Adapter\Swoole(port: System::getEnv('PORT', 80)); -$adapter - ->setPackageMaxLength(MAX_PACKAGE_LENGTH) - ->setWorkerNumber($workerNumber); - -$server = new Server($adapter); - -$server->onStart(function () use ($workerNumber) { - Console::success('Terminal WebSocket Proxy started successfully'); - Console::info('Listening on port: ' . System::getEnv('PORT', 80)); - Console::info('Worker processes: ' . $workerNumber); - Console::info('Max package length: ' . (MAX_PACKAGE_LENGTH / 1000) . 'KB'); - Console::info('Max runtime connections: ' . MAX_RUNTIME_CONNECTIONS); -}); - -$server->onOpen(function (int $connection, $request) use ($server, $runtimes, &$clients) { - try { - Console::info("New connection: {$connection}"); - - // Extract JWT from request - $token = $request->header['authorization'] ?? ''; - if (empty($token)) { - throw new Exception('Missing authentication token', 401); - } - - // Verify JWT and extract user information - $jwt = str_replace('Bearer ', '', $token); - $key = System::getEnv('_APP_OPENSSL_KEY_V1', ''); - $jwt = new JWT($key, 'HS256', 900, 0); - - try { - $payload = $jwt->decode($token); - $userId = $payload['userId'] ?? ''; - $sessionId = $payload['sessionId'] ?? ''; - - if (empty($userId) || empty($sessionId)) { - throw new Exception('Invalid JWT payload', 401); - } - } catch (\Exception $e) { - throw new Exception('Invalid JWT token', 401); - } - - // Get runtime details for user (this could come from your database/cache) - $runtimeHost = "runtime-{$userId}.internal"; // Example hostname - $runtimePort = 9000; - - // Create WebSocket connection to runtime - go(function () use ($server, $connection, &$clients, $runtimeHost, $runtimePort, $userId) { - try { - // $wsClient = new Client("ws://{$runtimeHost}:{$runtimePort}/", [ - // 'timeout' => 0, // Disable timeout for long-running connections - // 'filter' => ['text', 'binary', 'close'] // Only process these frame types - // ]); - - - $wsClient = new Client( - "ws://appwrite-traefik/v1/realtime", - [ - "headers" => [], - "timeout" => 30, - ] - ); - - // Store client connection - $clients[$connection] = [ - 'client' => $wsClient, - 'userId' => $userId - ]; - - // Forward messages from runtime back to client - while (true) { - try { - $message = $wsClient->receive(); - if ($message === null) { - // Connection closed normally - break; - } - $server->send([$connection], $message); - - // Yield to allow other coroutines to run - Swoole\Coroutine::yield(); - - } catch (\WebSocket\ConnectionException $e) { - Console::error("Runtime connection error for user {$userId}: " . $e->getMessage()); - break; - } - } - - // Cleanup on disconnect - $wsClient->close(); - unset($clients[$connection]); - $server->close($connection, CLOSE_NORMAL); - } catch (\WebSocket\ConnectionException $e) { - Console::error("Failed to connect to runtime for user {$userId}: " . $e->getMessage()); - $server->close($connection, CLOSE_SERVER_ERROR); - return; - } - }); - - // Send successful connection message - $server->send([$connection], json_encode([ - 'type' => 'connected', - 'data' => [ - 'userId' => $userId, - 'timestamp' => time() - ] - ])); - - } catch (Throwable $th) { - Console::error('Connection error: ' . $th->getMessage()); - - $server->send([$connection], json_encode([ - 'type' => 'error', - 'data' => [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() - ] - ])); - - $server->close($connection, CLOSE_POLICY_VIOLATION); - } -}); - -$server->onMessage(function (int $connection, string $message) use ($server, &$clients) { - try { - if (!isset($clients[$connection])) { - throw new Exception('Client not connected to runtime', 1008); - } - - $wsClient = $clients[$connection]['client']; - try { - // Forward message to runtime - $wsClient->send($message); - } catch (\WebSocket\ConnectionException $e) { - throw new Exception('Runtime connection lost: ' . $e->getMessage(), 1008); - } - - } catch (Throwable $th) { - $server->send([$connection], json_encode([ - 'type' => 'error', - 'data' => [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() - ] - ])); - - if ($th->getCode() === 1008) { - $server->close($connection, CLOSE_POLICY_VIOLATION); - } - } -}); - -$server->onClose(function (int $connection) use (&$clients) { - if (isset($clients[$connection])) { - $userId = $clients[$connection]['userId']; - $clients[$connection]['client']->close(); - unset($clients[$connection]); - Console::info("Closed connection for user {$userId}"); - } -}); - -$server->start(); \ No newline at end of file diff --git a/src/Appwrite/Auth/Hash/Argon2.php b/src/Appwrite/Auth/Hash/Argon2.php deleted file mode 100644 index c723b077b1..0000000000 --- a/src/Appwrite/Auth/Hash/Argon2.php +++ /dev/null @@ -1,47 +0,0 @@ -getOptions()); - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - return \password_verify($password, $hash); - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3]; - } -} diff --git a/src/Appwrite/Auth/Hash/Bcrypt.php b/src/Appwrite/Auth/Hash/Bcrypt.php deleted file mode 100644 index 8b6177f33a..0000000000 --- a/src/Appwrite/Auth/Hash/Bcrypt.php +++ /dev/null @@ -1,46 +0,0 @@ -getOptions()); - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - return \password_verify($password, $hash); - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return [ 'cost' => 8 ]; - } -} diff --git a/src/Appwrite/Auth/Hash/Md5.php b/src/Appwrite/Auth/Hash/Md5.php deleted file mode 100644 index 8ade3dd5e2..0000000000 --- a/src/Appwrite/Auth/Hash/Md5.php +++ /dev/null @@ -1,44 +0,0 @@ -hash($password) === $hash; - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return []; - } -} diff --git a/src/Appwrite/Auth/Hash/Phpass.php b/src/Appwrite/Auth/Hash/Phpass.php deleted file mode 100644 index 988c38cc8d..0000000000 --- a/src/Appwrite/Auth/Hash/Phpass.php +++ /dev/null @@ -1,290 +0,0 @@ - in 2004-2017 and placed in - * the public domain. Revised in subsequent years, still public domain. - * There's absolutely no warranty. - * The homepage URL for the source framework is: http://www.openwall.com/phpass/ - * Please be sure to update the Version line if you edit this file in any way. - * It is suggested that you leave the main version number intact, but indicate - * your project name (after the slash) and add your own revision information. - * Please do not change the "private" password hashing method implemented in - * here, thereby making your hashes incompatible. However, if you must, please - * change the hash type identifier (the "$P$") to something different. - * Obviously, since this code is in the public domain, the above are not - * requirements (there can be none), but merely suggestions. - * - * @author Solar Designer - * @copyright Copyright (C) 2017 All rights reserved. - * @license http://www.opensource.org/licenses/mit-license.html MIT License; see LICENSE.txt - */ - -namespace Appwrite\Auth\Hash; - -use Appwrite\Auth\Hash; - -/* - * PHPass accepted options: - * int iteration_count_log2; The Logarithmic cost value used when generating hash values indicating the number of rounds used to generate hashes - * string portable_hashes - * string random_state; The cached random state - * - * Reference: https://github.com/photodude/phpass -*/ -class Phpass extends Hash -{ - /** - * Alphabet used in itoa64 conversions. - * - * @var string - * @since 0.1.0 - */ - protected string $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - $randomState = \microtime(); - if (\function_exists('getmypid')) { - $randomState .= getmypid(); - } - - return ['iteration_count_log2' => 8, 'portable_hashes' => false, 'random_state' => $randomState]; - } - - /** - * @param string $password Input password to hash - * - * @return string hash - */ - public function hash(string $password): string - { - $options = $this->getDefaultOptions(); - - $random = ''; - if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) { - $random = $this->getRandomBytes(16, $options); - $hash = crypt($password, $this->gensaltBlowfish($random, $options)); - if (strlen($hash) === 60) { - return $hash; - } - } - if (strlen($random) < 6) { - $random = $this->getRandomBytes(6, $options); - } - $hash = $this->cryptPrivate($password, $this->gensaltPrivate($random, $options)); - if (strlen($hash) === 34) { - return $hash; - } - - /** - * Returning '*' on error is safe here, but would _not_ be safe - * in a crypt(3)-like function used _both_ for generating new - * hashes and for validating passwords against existing hashes. - */ - return '*'; - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - $verificationHash = $this->cryptPrivate($password, $hash); - if ($verificationHash[0] === '*') { - $verificationHash = crypt($password, $hash); - } - - /** - * This is not constant-time. In order to keep the code simple, - * for timing safety we currently rely on the salts being - * unpredictable, which they are at least in the non-fallback - * cases (that is, when we use /dev/urandom and bcrypt). - */ - return $hash === $verificationHash; - } - - /** - * @param int $count - * - * @return String $output - * @since 0.1.0 - * @throws Exception Thows an Exception if the $count parameter is not a positive integer. - */ - protected function getRandomBytes(int $count, array $options): string - { - if (!is_int($count) || $count < 1) { - throw new \Exception('Argument count must be a positive integer'); - } - $output = ''; - if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) { - $output = fread($fh, $count); - fclose($fh); - } - - if (strlen($output) < $count) { - $output = ''; - - for ($i = 0; $i < $count; $i += 16) { - $options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']); - $output .= md5($options['iteration_count_log2'], true); - } - - $output = substr($output, 0, $count); - } - - return $output; - } - - /** - * @param String $input - * @param int $count - * - * @return String $output - * @since 0.1.0 - * @throws Exception Thows an Exception if the $count parameter is not a positive integer. - */ - protected function encode64($input, $count) - { - if (!is_int($count) || $count < 1) { - throw new \Exception('Argument count must be a positive integer'); - } - $output = ''; - $i = 0; - do { - $value = ord($input[$i++]); - $output .= $this->itoa64[$value & 0x3f]; - if ($i < $count) { - $value |= ord($input[$i]) << 8; - } - $output .= $this->itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) { - break; - } - if ($i < $count) { - $value |= ord($input[$i]) << 16; - } - $output .= $this->itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) { - break; - } - $output .= $this->itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; - } - - /** - * @param String $input - * - * @return String $output - * @since 0.1.0 - */ - private function gensaltPrivate($input, $options) - { - $output = '$P$'; - $output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; - $output .= $this->encode64($input, 6); - - return $output; - } - - /** - * @param String $password - * @param String $setting - * - * @return String $output - * @since 0.1.0 - */ - private function cryptPrivate($password, $setting) - { - $output = '*0'; - if (substr($setting, 0, 2) === $output) { - $output = '*1'; - } - $id = substr($setting, 0, 3); - // We use "$P$", phpBB3 uses "$H$" for the same thing - if ($id !== '$P$' && $id !== '$H$') { - return $output; - } - $count_log2 = strpos($this->itoa64, $setting[3]); - if ($count_log2 < 7 || $count_log2 > 30) { - return $output; - } - $count = 1 << $count_log2; - $salt = substr($setting, 4, 8); - if (strlen($salt) !== 8) { - return $output; - } - /** - * We were kind of forced to use MD5 here since it's the only - * cryptographic primitive that was available in all versions of PHP - * in use. To implement our own low-level crypto in PHP - * would have result in much worse performance and - * consequently in lower iteration counts and hashes that are - * quicker to crack (by non-PHP code). - */ - $hash = md5($salt . $password, true); - do { - $hash = md5($hash . $password, true); - } while (--$count); - $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, 16); - - return $output; - } - - /** - * @param String $input - * - * @return String $output - * @since 0.1.0 - */ - private function gensaltBlowfish($input, $options) - { - /** - * This one needs to use a different order of characters and a - * different encoding scheme from the one in encode64() above. - * We care because the last character in our encoded string will - * only represent 2 bits. While two known implementations of - * bcrypt will happily accept and correct a salt string which - * has the 4 unused bits set to non-zero, we do not want to take - * chances and we also do not want to waste an additional byte - * of entropy. - */ - $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $output = '$2a$'; - $output .= chr(ord('0') + intval($options['iteration_count_log2'] / 10)); - $output .= chr(ord('0') + $options['iteration_count_log2'] % 10); - $output .= '$'; - $i = 0; - do { - $c1 = ord($input[$i++]); - $output .= $itoa64[$c1 >> 2]; - $c1 = ($c1 & 0x03) << 4; - if ($i >= 16) { - $output .= $itoa64[$c1]; - break; - } - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 4; - $output .= $itoa64[$c1]; - $c1 = ($c2 & 0x0f) << 2; - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 6; - $output .= $itoa64[$c1]; - $output .= $itoa64[$c2 & 0x3f]; - } while (1); - - return $output; - } -} diff --git a/src/Appwrite/Auth/Hash/Scrypt.php b/src/Appwrite/Auth/Hash/Scrypt.php deleted file mode 100644 index 821b1fba69..0000000000 --- a/src/Appwrite/Auth/Hash/Scrypt.php +++ /dev/null @@ -1,51 +0,0 @@ -getOptions(); - - return \scrypt($password, $options['salt'], $options['costCpu'], $options['costMemory'], $options['costParallel'], $options['length']); - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - return $hash === $this->hash($password); - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return [ 'costCpu' => 8, 'costMemory' => 14, 'costParallel' => 1, 'length' => 64 ]; - } -} diff --git a/src/Appwrite/Auth/Hash/Scryptmodified.php b/src/Appwrite/Auth/Hash/Scryptmodified.php deleted file mode 100644 index 7717f324e5..0000000000 --- a/src/Appwrite/Auth/Hash/Scryptmodified.php +++ /dev/null @@ -1,80 +0,0 @@ -getOptions(); - - $derivedKeyBytes = $this->generateDerivedKey($password); - $signerKeyBytes = \base64_decode($options['signerKey']); - - $hashedPassword = $this->hashKeys($signerKeyBytes, $derivedKeyBytes); - - return \base64_encode($hashedPassword); - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - return $this->hash($password) === $hash; - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return [ ]; - } - - private function generateDerivedKey(string $password) - { - $options = $this->getOptions(); - - $saltBytes = \base64_decode($options['salt']); - $saltSeparatorBytes = \base64_decode($options['saltSeparator']); - - $password = mb_convert_encoding($password, 'UTF-8'); - $derivedKey = \scrypt($password, $saltBytes . $saltSeparatorBytes, 16384, 8, 1, 64); - $derivedKey = \hex2bin($derivedKey); - - return $derivedKey; - } - - private function hashKeys($signerKeyBytes, $derivedKeyBytes): string - { - $key = \substr($derivedKeyBytes, 0, 32); - - $iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; - - $hash = \openssl_encrypt($signerKeyBytes, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv); - - return $hash; - } -} diff --git a/src/Appwrite/Auth/Hash/Sha.php b/src/Appwrite/Auth/Hash/Sha.php deleted file mode 100644 index c2ae3b52c1..0000000000 --- a/src/Appwrite/Auth/Hash/Sha.php +++ /dev/null @@ -1,50 +0,0 @@ -getOptions()['version']; - - return \hash($algo, $password); - } - - /** - * @param string $password Input password to validate - * @param string $hash Hash to verify password against - * - * @return boolean true if password matches hash - */ - public function verify(string $password, string $hash): bool - { - return $this->hash($password) === $hash; - } - - /** - * Get default options for specific hashing algo - * - * @return array options named array - */ - public function getDefaultOptions(): array - { - return [ 'version' => 'sha3-512' ]; - } -} From 0483c7efb5502ce51a1bd464f6db92a9d347b320 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 29 Apr 2025 20:44:05 +0200 Subject: [PATCH 035/385] Merge fixes --- app/controllers/api/account.php | 46 ++---------------- app/init/resources.php | 3 -- composer.json | 2 +- composer.lock | 48 ++++++++----------- src/Appwrite/Migration/Version/V23.php | 11 ----- .../Functions/Http/Executions/Create.php | 8 +--- 6 files changed, 27 insertions(+), 91 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 463bd19890..7939e21818 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -158,15 +158,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc ->trigger(); }; - -<<<<<<< HEAD $createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken) { - $roles = Authorization::getRoles(); - $isPrivilegedUser = Auth::isPrivilegedUser($roles); - $isAppUser = Auth::isAppUser($roles); -======= -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { ->>>>>>> origin/1.7.x /** @var Utopia\Database\Document $user */ $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -287,11 +279,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setAttribute('current', true) ->setAttribute('countryName', $countryName) ->setAttribute('expire', $expire) -<<<<<<< HEAD - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') -======= - ->setAttribute('secret', Auth::encodeSession($user->getId(), $sessionSecret)) ->>>>>>> origin/1.7.x + ->setAttribute('secret', $encoded) ; $response->dynamic($session, Response::MODEL_SESSION); @@ -999,11 +987,7 @@ App::post('/v1/account/sessions/email') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) -<<<<<<< HEAD - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') -======= - ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ->>>>>>> origin/1.7.x + ->setAttribute('secret', $encoded) ; $queueForEvents @@ -1166,11 +1150,7 @@ App::post('/v1/account/sessions/anonymous') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) -<<<<<<< HEAD - ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : '') -======= - ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ->>>>>>> origin/1.7.x + ->setAttribute('secret', $encoded) ; $response->dynamic($session, Response::MODEL_SESSION); @@ -2654,18 +2634,8 @@ App::post('/v1/account/tokens/phone') $queueForEvents ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); -<<<<<<< HEAD - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - - // Hide secret for clients - $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $encoded : ''); -======= // Encode secret for clients - $token->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)); ->>>>>>> origin/1.7.x + $token->setAttribute('secret', $encoded); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -3532,16 +3502,8 @@ App::post('/v1/account/verification') throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } -<<<<<<< HEAD - $roles = Authorization::getRoles(); - $isPrivilegedUser = Auth::isPrivilegedUser($roles); - $isAppUser = Auth::isAppUser($roles); $verificationSecret = $proofForToken->generate(); $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); -======= - $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); - $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); ->>>>>>> origin/1.7.x $verification = new Document([ '$id' => ID::unique(), diff --git a/app/init/resources.php b/app/init/resources.php index c90c72b3d7..00fde2daf2 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -926,7 +926,6 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); -<<<<<<< HEAD App::setResource('store', function (): Store { return new Store(); @@ -957,7 +956,6 @@ App::setResource('proofForCode', function (): Code { $code->setHash(new Sha()); return $code; }); -======= App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); App::setResource('resourceToken', function ($project, $dbForProject, $request) { @@ -1002,4 +1000,3 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) { } return new Document([]); }, ['project', 'dbForProject', 'request']); ->>>>>>> origin/1.7.x diff --git a/composer.json b/composer.json index 3bf2a8c759..2991742d47 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.19.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/auth": "dev-dev", + "utopia-php/auth": "0.3.0", "utopia-php/abuse": "0.52.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.55.*", diff --git a/composer.lock b/composer.lock index 3daaf6e3e7..dbba4679fe 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": "51959289a3f882160f5a9eeb605d41d7", + "content-hash": "2ed8b411e74c4f6e7d514749f28e933a", "packages": [ { "name": "adhocore/jwt", @@ -3300,16 +3300,16 @@ }, { "name": "utopia-php/auth", - "version": "dev-dev", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "19fb580de44fac5928f9c0211fd0fdfd5022efdb" + "reference": "231e1e0bb97e79438399ffe4de3079063b5dc0b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/19fb580de44fac5928f9c0211fd0fdfd5022efdb", - "reference": "19fb580de44fac5928f9c0211fd0fdfd5022efdb", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/231e1e0bb97e79438399ffe4de3079063b5dc0b1", + "reference": "231e1e0bb97e79438399ffe4de3079063b5dc0b1", "shasum": "" }, "require": { @@ -3349,9 +3349,9 @@ ], "support": { "issues": "https://github.com/utopia-php/auth/issues", - "source": "https://github.com/utopia-php/auth/tree/dev" + "source": "https://github.com/utopia-php/auth/tree/0.3.0" }, - "time": "2025-03-19T06:47:02+00:00" + "time": "2025-03-09T21:44:43+00:00" }, { "name": "utopia-php/cache", @@ -3760,16 +3760,16 @@ }, { "name": "utopia-php/fetch", - "version": "0.4.1", + "version": "0.4.2", "source": { "type": "git", "url": "https://github.com/utopia-php/fetch.git", - "reference": "65095dac14037db0c822fb5e209e5bd3187a0303" + "reference": "83986d1be75a2fae4e684107fe70dd78a8e19b77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/65095dac14037db0c822fb5e209e5bd3187a0303", - "reference": "65095dac14037db0c822fb5e209e5bd3187a0303", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/83986d1be75a2fae4e684107fe70dd78a8e19b77", + "reference": "83986d1be75a2fae4e684107fe70dd78a8e19b77", "shasum": "" }, "require": { @@ -3793,9 +3793,9 @@ "description": "A simple library that provides an interface for making HTTP Requests.", "support": { "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.4.1" + "source": "https://github.com/utopia-php/fetch/tree/0.4.2" }, - "time": "2025-04-14T07:34:27+00:00" + "time": "2025-04-25T13:48:02+00:00" }, { "name": "utopia-php/framework", @@ -5330,16 +5330,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -5378,7 +5378,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -5386,7 +5386,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nikic/php-parser", @@ -8284,13 +8284,7 @@ ], "aliases": [], "minimum-stability": "stable", -<<<<<<< HEAD - "stability-flags": { - "utopia-php/auth": 20 - }, -======= - "stability-flags": [], ->>>>>>> origin/1.7.x + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8314,5 +8308,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php index f06947f95c..dec7e8e9d3 100644 --- a/src/Appwrite/Migration/Version/V23.php +++ b/src/Appwrite/Migration/Version/V23.php @@ -5,11 +5,8 @@ namespace Appwrite\Migration\Version; use Appwrite\Migration\Migration; use Exception; use Throwable; -<<<<<<< HEAD -======= use Utopia\CLI\Console; use Utopia\Database\Database; ->>>>>>> origin/1.7.x class V23 extends Migration { @@ -18,9 +15,6 @@ class V23 extends Migration */ public function execute(): void { -<<<<<<< HEAD - // TBD -======= /** * Disable SubQueries for Performance. */ @@ -34,7 +28,6 @@ class V23 extends Migration Console::info('Migrating Collections'); $this->migrateCollections(); ->>>>>>> origin/1.7.x } /** @@ -45,9 +38,6 @@ class V23 extends Migration */ private function migrateCollections(): void { -<<<<<<< HEAD - // TBD -======= $internalProjectId = $this->project->getInternalId(); $collectionType = match ($internalProjectId) { 'console' => 'console', @@ -75,6 +65,5 @@ class V23 extends Migration usleep(50000); } ->>>>>>> origin/1.7.x } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index ec6f214b12..9298cb066b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -96,12 +96,9 @@ class Create extends Base ->inject('queueForStatsUsage') ->inject('queueForFunctions') ->inject('geodb') -<<<<<<< HEAD ->inject('store') ->inject('proofForToken') -======= ->inject('executor') ->>>>>>> origin/1.7.x ->callback([$this, 'action']); } @@ -123,12 +120,9 @@ class Create extends Base StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, -<<<<<<< HEAD Store $store, - Token $proofForToken -======= + Token $proofForToken, Executor $executor ->>>>>>> origin/1.7.x ) { $async = \strval($async) === 'true' || \strval($async) === '1'; From 6f861a91ee22d269db0117982caea88c61f69073 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 29 Apr 2025 21:33:59 +0200 Subject: [PATCH 036/385] Updated auth lib --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 2991742d47..907625f679 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.19.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/auth": "0.3.0", + "utopia-php/auth": "0.4.0", "utopia-php/abuse": "0.52.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.55.*", diff --git a/composer.lock b/composer.lock index dbba4679fe..b1aa0690d3 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": "2ed8b411e74c4f6e7d514749f28e933a", + "content-hash": "f86338f8299f81c9fe28fb41bf59a00b", "packages": [ { "name": "adhocore/jwt", @@ -3300,16 +3300,16 @@ }, { "name": "utopia-php/auth", - "version": "0.3.0", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/auth.git", - "reference": "231e1e0bb97e79438399ffe4de3079063b5dc0b1" + "reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/231e1e0bb97e79438399ffe4de3079063b5dc0b1", - "reference": "231e1e0bb97e79438399ffe4de3079063b5dc0b1", + "url": "https://api.github.com/repos/utopia-php/auth/zipball/02415e1a89cdbc14e3e16a7856ecf7f868869449", + "reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449", "shasum": "" }, "require": { @@ -3349,9 +3349,9 @@ ], "support": { "issues": "https://github.com/utopia-php/auth/issues", - "source": "https://github.com/utopia-php/auth/tree/0.3.0" + "source": "https://github.com/utopia-php/auth/tree/0.4.0" }, - "time": "2025-03-09T21:44:43+00:00" + "time": "2025-04-29T19:29:28+00:00" }, { "name": "utopia-php/cache", From fc8326417b469ba9f58323aad99b622b0ebefedf Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 23 May 2025 19:20:26 +0530 Subject: [PATCH 037/385] update: z-index to be the highest! --- src/Appwrite/Transformation/Adapter/Preview.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Transformation/Adapter/Preview.php b/src/Appwrite/Transformation/Adapter/Preview.php index 70af19a188..7c08777413 100644 --- a/src/Appwrite/Transformation/Adapter/Preview.php +++ b/src/Appwrite/Transformation/Adapter/Preview.php @@ -47,7 +47,7 @@ class Preview extends Adapter position: fixed; right: 16px; bottom: 16px; - z-index: 1; + z-index: calc(infinity); border-radius: var(--border-radius-S, 8px); border: var(--border-width-S, 1px) solid var(--color-border-neutral, #EDEDF0); background: var(--color-bgColor-neutral-primary, #FFF); From b556846be3441e1daa3bba3a4c0cf438a31924d7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:01:39 -0400 Subject: [PATCH 038/385] Add txn roles --- app/config/roles.php | 130 ++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/app/config/roles.php b/app/config/roles.php index bccc2837f5..bbe970f05d 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -3,105 +3,107 @@ use Appwrite\Auth\Auth; $member = [ - 'global', - 'public', - 'home', - 'console', - 'graphql', - 'sessions.write', 'account', - 'teams.read', - 'teams.write', + 'assistant.read', + 'avatars.read', + 'console', 'documents.read', 'documents.write', - 'files.read', - 'files.write', - 'projects.read', - 'locale.read', - 'avatars.read', 'execution.read', 'execution.write', + 'files.read', + 'files.write', + 'global', + 'graphql', + 'home', + 'locale.read', + 'projects.read', + 'public', + 'rules.read', + 'sessions.write', + 'subscribers.read', + 'subscribers.write', 'targets.read', 'targets.write', - 'subscribers.write', - 'subscribers.read', - 'assistant.read', - 'rules.read' + 'teams.read', + 'teams.write', ]; $admins = [ - 'global', - 'graphql', - 'sessions.write', - 'teams.read', - 'teams.write', - 'documents.read', - 'documents.write', - 'files.read', - 'files.write', + 'avatars.read', 'buckets.read', 'buckets.write', - 'users.read', - 'users.write', - 'databases.read', - 'databases.write', 'collections.read', 'collections.write', + 'databases.read', + 'databases.write', + 'documents.read', + 'documents.write', + 'execution.read', + 'execution.write', + 'files.read', + 'files.write', + 'functions.read', + 'functions.write', + 'global', + 'graphql', + 'health.read', + 'keys.read', + 'keys.write', + 'locale.read', + 'log.read', + 'log.write', + 'messages.read', + 'messages.write', + 'migrations.read', + 'migrations.write', 'platforms.read', 'platforms.write', 'projects.write', - 'keys.read', - 'keys.write', - 'webhooks.read', - 'webhooks.write', - 'locale.read', - 'avatars.read', - 'health.read', - 'functions.read', - 'functions.write', - 'sites.read', - 'sites.write', - 'log.read', - 'log.write', - 'execution.read', - 'execution.write', + 'providers.read', + 'providers.write', 'rules.read', 'rules.write', - 'migrations.read', - 'migrations.write', - 'vcs.read', - 'vcs.write', + 'sessions.write', + 'sites.read', + 'sites.write', + 'subscribers.read', + 'subscribers.write', 'targets.read', 'targets.write', - 'providers.write', - 'providers.read', - 'messages.write', - 'messages.read', - 'topics.write', - 'topics.read', - 'subscribers.write', - 'subscribers.read', + 'teams.read', + 'teams.write', 'tokens.read', 'tokens.write', + 'topics.read', + 'topics.write', + 'transactions.read', + 'transactions.write', + 'users.read', + 'users.write', + 'vcs.read', + 'vcs.write', + 'webhooks.read', + 'webhooks.write', ]; return [ Auth::USER_ROLE_GUESTS => [ 'label' => 'Guests', 'scopes' => [ - 'global', - 'public', - 'home', + 'avatars.read', 'console', - 'graphql', - 'sessions.write', 'documents.read', 'documents.write', + 'execution.write', 'files.read', 'files.write', + 'global', + 'graphql', + 'home', 'locale.read', - 'avatars.read', - 'execution.write', + 'public', + 'sessions.write', ], ], Auth::USER_ROLE_USERS => [ From fc4b82d017a85730b37e4c17266598c96725c38a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:01:45 -0400 Subject: [PATCH 039/385] Add txn scopes --- app/config/scopes.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/config/scopes.php b/app/config/scopes.php index 7dea7b1cd5..69cc94a009 100644 --- a/app/config/scopes.php +++ b/app/config/scopes.php @@ -40,6 +40,12 @@ return [ // List of publicly visible scopes 'indexes.write' => [ 'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes', ], + 'transactions.read' => [ + 'description' => 'Access to read your project\'s database transactions', + ], + 'transactions.write' => [ + 'description' => 'Access to create, update, and delete your project\'s database transactions', + ], 'documents.read' => [ 'description' => 'Access to read your project\'s database documents', ], From 11ce3941811f97521b6000390b3d70ef648e797c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:01:56 -0400 Subject: [PATCH 040/385] Add txn errors --- app/config/errors.php | 37 +++++++++++++++++++++++++++++++ src/Appwrite/Extend/Exception.php | 10 +++++++++ 2 files changed, 47 insertions(+) diff --git a/app/config/errors.php b/app/config/errors.php index 8365e8c705..7a48e7f46f 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -810,6 +810,43 @@ return [ 'code' => 409, ], + /** Transactions */ + Exception::TRANSACTION_NOT_FOUND => [ + 'name' => Exception::TRANSACTION_NOT_FOUND, + 'description' => 'Transaction with the requested ID could not be found.', + 'code' => 404, + ], + Exception::TRANSACTION_ALREADY_EXISTS => [ + 'name' => Exception::TRANSACTION_ALREADY_EXISTS, + 'description' => 'Transaction with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.', + 'code' => 409, + ], + Exception::TRANSACTION_INVALID => [ + 'name' => Exception::TRANSACTION_INVALID, + 'description' => 'The transaction is invalid. Please check the transaction data and try again.', + 'code' => 400, + ], + Exception::TRANSACTION_EXPIRED => [ + 'name' => Exception::TRANSACTION_EXPIRED, + 'description' => 'The transaction has expired. Please create a new transaction and try again.', + 'code' => 410, + ], + Exception::TRANSACTION_CONFLICT => [ + 'name' => Exception::TRANSACTION_CONFLICT, + 'description' => 'The transaction has a conflict. Please resolve the conflict and try again.', + 'code' => 409, + ], + Exception::TRANSACTION_LIMIT_EXCEEDED => [ + 'name' => Exception::TRANSACTION_LIMIT_EXCEEDED, + 'description' => 'The maximum number of transactions has been reached.', + 'code' => 400, + ], + Exception::TRANSACTION_NOT_READY => [ + 'name' => Exception::TRANSACTION_NOT_READY, + 'description' => 'The transaction is not ready yet. Please try again later.', + 'code' => 400, + ], + /** Project Errors */ Exception::PROJECT_NOT_FOUND => [ 'name' => Exception::PROJECT_NOT_FOUND, diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 3af6d9962c..2e9911683c 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -229,6 +229,16 @@ class Exception extends \Exception public const INDEX_INVALID = 'index_invalid'; public const INDEX_DEPENDENCY = 'index_dependency'; + /** Transactions */ + public const TRANSACTION_NOT_FOUND = 'transaction_not_found'; + public const TRANSACTION_ALREADY_EXISTS = 'transaction_already_exists'; + public const TRANSACTION_INVALID = 'transaction_invalid'; + public const TRANSACTION_EXPIRED = 'transaction_expired'; + public const TRANSACTION_CONFLICT = 'transaction_conflict'; + public const TRANSACTION_LIMIT_EXCEEDED = 'transaction_limit_exceeded'; + public const TRANSACTION_NOT_READY = 'transaction_not_ready'; + + /** Projects */ public const PROJECT_NOT_FOUND = 'project_not_found'; public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled'; From c11fd38f0fa3500c46b72c12596add82ea673547 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:02:17 -0400 Subject: [PATCH 041/385] Add txn models --- src/Appwrite/SDK/Response.php | 3 +- .../Utopia/Database/Validator/Operation.php | 76 +++++ src/Appwrite/Utopia/Response.php | 285 +++++++++--------- .../Utopia/Response/Model/Transaction.php | 54 ++++ 4 files changed, 271 insertions(+), 147 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Operation.php create mode 100644 src/Appwrite/Utopia/Response/Model/Transaction.php diff --git a/src/Appwrite/SDK/Response.php b/src/Appwrite/SDK/Response.php index e87813024b..2b034691a8 100644 --- a/src/Appwrite/SDK/Response.php +++ b/src/Appwrite/SDK/Response.php @@ -2,12 +2,11 @@ namespace Appwrite\SDK; -class Response +readonly class Response { /** * @param int $code * @param string|array $model - * @param string $description */ public function __construct( private int $code, diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php new file mode 100644 index 0000000000..3f9a15673a --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -0,0 +1,76 @@ +description; + } + + public function isArray(): bool + { + return true; + } + + /** + * @param mixed $value + */ + public function isValid($value): bool + { + // Must be array‑like + if (!\is_array($value)) { + $this->description = 'Value must be an array'; + return false; + } + + // Mandatory keys + $required = ['databaseId', 'collectionId', 'action', 'payload']; + foreach ($required as $key) { + if (!\array_key_exists($key, $value)) { + $this->description = "Missing required key: {$key}"; + return false; + } + } + + // databaseId / collectionId / action must be non‑empty strings + foreach (['databaseId', 'collectionId', 'action'] as $key) { + if (!\is_string($value[$key]) || \trim($value[$key]) === '') { + $this->description = "Key '{$key}' must be a non‑empty string"; + return false; + } + } + + // Validate action + if (!\in_array($value['action'], $this->actions, true)) { + $this->description = "Key 'action' must be one of: " . \implode(', ', $this->actions); + return false; + } + + // Payload must be array (can be empty) + if (!\is_array($value['payload'])) { + $this->description = "Key 'payload' must be an array"; + return false; + } + + return true; + } + + public function getType(): string + { + return self::TYPE_OBJECT; + } +} diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 3d69ac1291..a376dd9cb6 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -144,7 +144,6 @@ class Response extends SwooleResponse public const MODEL_METRIC_LIST = 'metricList'; public const MODEL_METRIC_BREAKDOWN = 'metricBreakdown'; public const MODEL_ERROR_DEV = 'errorDev'; - public const MODEL_BASE_LIST = 'baseList'; public const MODEL_USAGE_DATABASES = 'usageDatabases'; public const MODEL_USAGE_DATABASE = 'usageDatabase'; public const MODEL_USAGE_COLLECTION = 'usageCollection'; @@ -166,6 +165,8 @@ class Response extends SwooleResponse public const MODEL_INDEX_LIST = 'indexList'; public const MODEL_DOCUMENT = 'document'; public const MODEL_DOCUMENT_LIST = 'documentList'; + public const MODEL_TRANSACTION = 'transaction'; + public const MODEL_TRANSACTION_LIST = 'transactionList'; // Database Attributes public const MODEL_ATTRIBUTE = 'attribute'; @@ -337,13 +338,6 @@ class Response extends SwooleResponse // Console public const MODEL_CONSOLE_VARIABLES = 'consoleVariables'; - // Deprecated - public const MODEL_PERMISSIONS = 'permissions'; - public const MODEL_RULE = 'rule'; - public const MODEL_TASK = 'task'; - public const MODEL_DOMAIN = 'domain'; - public const MODEL_DOMAIN_LIST = 'domainList'; - // Tests (keep last) public const MODEL_MOCK = 'mock'; @@ -365,7 +359,7 @@ class Response extends SwooleResponse /** * Response constructor. * - * @param float $time + * @param SwooleHTTPResponse $response */ public function __construct(SwooleHTTPResponse $response) { @@ -376,165 +370,166 @@ class Response extends SwooleResponse ->setModel(new Error()) ->setModel(new ErrorDev()) // Lists - ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) - ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) - ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) - ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) - ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) - ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) - ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) - ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) - ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) - ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET)) - ->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN)) - ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) - ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) - ->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE)) - ->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE)) - ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) - ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) - ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) - ->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK)) - ->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME)) - ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) - ->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK)) - ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME)) - ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT)) - ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION)) - ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) - ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) - ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) - ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) - ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) + ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) + ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET)) + ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) - ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) + ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) - ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) - ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) - ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) - ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) - ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) + ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) + ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT)) + ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) + ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) + ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION)) + ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) + ->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK)) + ->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK)) + ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) + ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) + ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) + ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) + ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) + ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) - ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) - ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) - ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) + ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) + ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) + ->setModel(new BaseList('Message List', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) + ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) + ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) + ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) + ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) + ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) + ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) + ->setModel(new BaseList('Provider List', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) + ->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN)) + ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) + ->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME)) + ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME)) + ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) + ->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE)) + ->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE)) + ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) + ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) - ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) - ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) - ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) + ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) + ->setModel(new BaseList('Topic List', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) + ->setModel(new BaseList('Transaction List', self::MODEL_TRANSACTION_LIST, 'transactions', self::MODEL_TRANSACTION)) + ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) + ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) + ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) // Entities - ->setModel(new Database()) - ->setModel(new Collection()) - ->setModel(new Attribute()) - ->setModel(new AttributeList()) - ->setModel(new AttributeString()) - ->setModel(new AttributeInteger()) - ->setModel(new AttributeFloat()) - ->setModel(new AttributeBoolean()) - ->setModel(new AttributeEmail()) - ->setModel(new AttributeEnum()) - ->setModel(new AttributeIP()) - ->setModel(new AttributeURL()) - ->setModel(new AttributeDatetime()) - ->setModel(new AttributeRelationship()) - ->setModel(new Index()) - ->setModel(new ModelDocument()) - ->setModel(new Log()) - ->setModel(new User()) - ->setModel(new AlgoMd5()) - ->setModel(new AlgoSha()) - ->setModel(new AlgoPhpass()) + ->setModel(new Account()) + ->setModel(new AlgoArgon2()) ->setModel(new AlgoBcrypt()) + ->setModel(new AlgoMd5()) + ->setModel(new AlgoPhpass()) ->setModel(new AlgoScrypt()) ->setModel(new AlgoScryptModified()) - ->setModel(new AlgoArgon2()) - ->setModel(new Account()) - ->setModel(new Preferences()) - ->setModel(new Session()) + ->setModel(new AlgoSha()) + ->setModel(new Attribute()) + ->setModel(new AttributeBoolean()) + ->setModel(new AttributeDatetime()) + ->setModel(new AttributeEmail()) + ->setModel(new AttributeEnum()) + ->setModel(new AttributeFloat()) + ->setModel(new AttributeIP()) + ->setModel(new AttributeInteger()) + ->setModel(new AttributeList()) + ->setModel(new AttributeRelationship()) + ->setModel(new AttributeString()) + ->setModel(new AttributeURL()) + ->setModel(new AuthProvider()) + ->setModel(new Branch()) + ->setModel(new Bucket()) + ->setModel(new Collection()) + ->setModel(new ConsoleVariables()) + ->setModel(new Continent()) + ->setModel(new Country()) + ->setModel(new Currency()) + ->setModel(new Database()) + ->setModel(new Deployment()) + ->setModel(new DetectionFramework()) + ->setModel(new DetectionRuntime()) + ->setModel(new DevKey()) + ->setModel(new Execution()) + ->setModel(new File()) + ->setModel(new Framework()) + ->setModel(new FrameworkAdapter()) + ->setModel(new Func()) + ->setModel(new Headers()) + ->setModel(new HealthAntivirus()) + ->setModel(new HealthCertificate()) + ->setModel(new HealthQueue()) + ->setModel(new HealthStatus()) + ->setModel(new HealthTime()) + ->setModel(new HealthVersion()) ->setModel(new Identity()) - ->setModel(new Token()) + ->setModel(new Index()) + ->setModel(new Installation()) ->setModel(new JWT()) + ->setModel(new Key()) + ->setModel(new Language()) ->setModel(new Locale()) ->setModel(new LocaleCode()) - ->setModel(new File()) - ->setModel(new Bucket()) - ->setModel(new ResourceToken()) - ->setModel(new Team()) + ->setModel(new Log()) + ->setModel(new MFAChallenge()) + ->setModel(new MFAFactors()) + ->setModel(new MFARecoveryCodes()) + ->setModel(new MFAType()) ->setModel(new Membership()) - ->setModel(new Site()) - ->setModel(new TemplateSite()) - ->setModel(new TemplateFramework()) - ->setModel(new Func()) - ->setModel(new TemplateFunction()) - ->setModel(new TemplateRuntime()) - ->setModel(new TemplateVariable()) - ->setModel(new Installation()) + ->setModel(new Message()) + ->setModel(new Metric()) + ->setModel(new MetricBreakdown()) + ->setModel(new Migration()) + ->setModel(new MigrationFirebaseProject()) + ->setModel(new MigrationReport()) + ->setModel(new MockNumber()) + ->setModel(new ModelDocument()) + ->setModel(new Phone()) + ->setModel(new Platform()) + ->setModel(new Preferences()) + ->setModel(new Project()) + ->setModel(new Provider()) ->setModel(new ProviderRepository()) ->setModel(new ProviderRepositoryFramework()) ->setModel(new ProviderRepositoryRuntime()) - ->setModel(new DetectionFramework()) - ->setModel(new DetectionRuntime()) - ->setModel(new VcsContent()) - ->setModel(new Branch()) - ->setModel(new Runtime()) - ->setModel(new Framework()) - ->setModel(new FrameworkAdapter()) - ->setModel(new Deployment()) - ->setModel(new Execution()) - ->setModel(new Project()) - ->setModel(new Webhook()) - ->setModel(new Key()) - ->setModel(new DevKey()) - ->setModel(new MockNumber()) - ->setModel(new AuthProvider()) - ->setModel(new Platform()) - ->setModel(new Variable()) - ->setModel(new Country()) - ->setModel(new Continent()) - ->setModel(new Language()) - ->setModel(new Currency()) - ->setModel(new Phone()) - ->setModel(new HealthAntivirus()) - ->setModel(new HealthQueue()) - ->setModel(new HealthStatus()) - ->setModel(new HealthCertificate()) - ->setModel(new HealthTime()) - ->setModel(new HealthVersion()) - ->setModel(new Metric()) - ->setModel(new MetricBreakdown()) - ->setModel(new UsageDatabases()) - ->setModel(new UsageDatabase()) - ->setModel(new UsageCollection()) - ->setModel(new UsageUsers()) - ->setModel(new UsageStorage()) - ->setModel(new UsageBuckets()) - ->setModel(new UsageFunctions()) - ->setModel(new UsageFunction()) - ->setModel(new UsageSites()) - ->setModel(new UsageSite()) - ->setModel(new UsageProject()) - ->setModel(new Headers()) - ->setModel(new Specification()) + ->setModel(new ResourceToken()) ->setModel(new Rule()) - ->setModel(new TemplateSMS()) - ->setModel(new TemplateEmail()) - ->setModel(new ConsoleVariables()) - ->setModel(new MFAChallenge()) - ->setModel(new MFARecoveryCodes()) - ->setModel(new MFAType()) - ->setModel(new MFAFactors()) - ->setModel(new Provider()) - ->setModel(new Message()) - ->setModel(new Topic()) + ->setModel(new Runtime()) + ->setModel(new Session()) + ->setModel(new Site()) + ->setModel(new Specification()) ->setModel(new Subscriber()) ->setModel(new Target()) - ->setModel(new Migration()) - ->setModel(new MigrationReport()) - ->setModel(new MigrationFirebaseProject()) + ->setModel(new Team()) + ->setModel(new TemplateEmail()) + ->setModel(new TemplateFramework()) + ->setModel(new TemplateFunction()) + ->setModel(new TemplateRuntime()) + ->setModel(new TemplateSMS()) + ->setModel(new TemplateSite()) + ->setModel(new TemplateVariable()) + ->setModel(new Token()) + ->setModel(new Topic()) + ->setModel(new UsageBuckets()) + ->setModel(new UsageCollection()) + ->setModel(new UsageDatabase()) + ->setModel(new UsageDatabases()) + ->setModel(new UsageFunction()) + ->setModel(new UsageFunctions()) + ->setModel(new UsageProject()) + ->setModel(new UsageSite()) + ->setModel(new UsageSites()) + ->setModel(new UsageStorage()) + ->setModel(new UsageUsers()) + ->setModel(new User()) + ->setModel(new Variable()) + ->setModel(new VcsContent()) + ->setModel(new Webhook()) // Tests (keep last) ->setModel(new Mock()); diff --git a/src/Appwrite/Utopia/Response/Model/Transaction.php b/src/Appwrite/Utopia/Response/Model/Transaction.php new file mode 100644 index 0000000000..c6eafd18b9 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/Transaction.php @@ -0,0 +1,54 @@ +addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Transaction ID.', + 'default' => '', + 'example' => '259125845563242502', + ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Transaction creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Transaction update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('status', [ + 'type' => self::TYPE_STRING, + 'description' => 'Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.', + 'default' => 'pending', + 'example' => 'pending', + ]) + ->addRule('expiresAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Expiration time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]); + } + + public function getName(): string + { + return 'Transaction'; + } + + public function getType(): string + { + return Response::MODEL_TRANSACTION; + } +} From cfc6d5fe2b23f80a19ff194616ed5d886a086b06 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:02:32 -0400 Subject: [PATCH 042/385] Add txn collections --- app/config/collections/projects.php | 148 ++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 48a0938a1c..5e0b1b17b5 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2510,4 +2510,152 @@ return [ ], ], ], + + 'transactions' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('transactions'), + 'name' => 'Transactions', + 'attributes' => [ + [ + '$id' => ID::custom('status'), + 'type' => Database::VAR_STRING, + 'size' => 16, // pending | committing | committed | rolled_back | failed + 'signed' => true, + 'required' => false, + 'default' => 'pending', + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('expiresAt'), + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('committedAt'), + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('rolledBackAt'), + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_status'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['status'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_expires'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['expiresAt'], + 'lengths' => [], + 'orders' => [Database::ORDER_DESC], + ], + ], + ], + + 'transactionLogs' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('transactionLogs'), + 'name' => 'Transaction Logs', + 'attributes' => [ + [ + '$id' => ID::custom('transactionInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('databaseInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('collectionInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('documentId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('action'), + 'type' => Database::VAR_STRING, + 'size' => 32, // create | update | upsert | increment | decrement | delete + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('data'), + 'type' => Database::VAR_STRING, + 'size' => 65535, + 'signed' => false, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => ['json'], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_transaction'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['transactionInternalId'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_db_coll'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['databaseId', 'collectionId'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], ]; From 4e2727292eb35333a1051f98f7cf6ba5d6dd135e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:03:33 -0400 Subject: [PATCH 043/385] Add txn routes --- app/controllers/api/databases.php | 233 ++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 0e85171772..31b0585a7e 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1536,6 +1536,239 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); +App::post('/v1/databases/transactions') + ->desc('Create transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'createTransaction', + description: '/docs/references/databases/create-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('ttl', 300, new Integer(), 'Seconds before the transaction expires.', true) + ->inject('response') + ->inject('dbForProject') + ->action(function (int $ttl, Response $response, Database $dbForProject) { + $transaction = $dbForProject->createDocument('transactions', new Document([ + '$id' => ID::unique(), + 'status' => 'pending', + 'expiresAt' => DateTime::addSeconds(new \DateTime(), $ttl), + ])); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($transaction, Response::MODEL_TRANSACTION); + }); + +App::post('/v1/databases/transactions/:transactionId/operations') + ->desc('Add operations to transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'createOperations', + description: '/docs/references/databases/create-operations.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('operations', [], new ArrayList(new Operation()), 'Array of staged operations.', true) + ->inject('response') + ->inject('dbForProject') + ->action(function (string $transactionId, array $operations, Response $response, Database $dbForProject) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty() || $transaction['status'] !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + $staged = []; + foreach ($operations as $op) { + $staged[] = new Document([ + '$id' => ID::unique(), + 'transactionId' => $transactionId, + 'databaseId' => $op['databaseId'] ?? null, + 'collectionId' => $op['collectionId'] ?? null, + 'documentId' => $op['documentId'] ?? null, + 'action' => $op['action'], + 'data' => $op['data'] ?? [], + ]); + } + + $dbForProject->createDocuments('transactionLogs', $staged); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($transaction, Response::MODEL_TRANSACTION); + }); + +App::get('/v1/databases/transactions/:transactionId') + ->desc('Get transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'getTransaction', + description: '/docs/references/databases/get-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->action(function (string $transactionId, Response $response, Database $dbForProject) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->dynamic($transaction, Response::MODEL_TRANSACTION); + }); + +App::patch('/v1/databases/transactions/:transactionId') + ->desc('Update transaction (commit / rollback)') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'updateTransaction', + description: '/docs/references/databases/update-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('action', '', new WhiteList(['commit','rollback']), 'Action to take, commit or rollback.') + ->inject('response') + ->inject('dbForProject') + ->inject('project') + ->action(function (string $transactionId, string $action, ?string $reason, Response $response, Database $dbForProject, Document $project) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); + } + + switch ($action) { + case 'commit': + // Get staged operations + $operations = $dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc('$sequence'), + ]); + + $creates = $updates = $deletes = []; + + foreach ($operations as $operation) { + $databaseId = $operation['databaseInternalId']; + $collectionId = $operation['collectionInternalId']; + $documentId = $operation['documentInternalId']; + + switch ($operation['action']) { + case 'create': + $creates[$databaseId][$collectionId][] = new Document([ + '$id' => $documentId ?? ID::unique(), + ...$operation['data'] + ]); + break; + case 'update': + case 'upsert': + $updates[$databaseId][$collectionId][] = new Document([ + '$id' => $documentId, + ...$operation['data'], + ]); + break; + case 'delete': + $deletes[$databaseId][$collectionId][] = $documentId; + break; + } + } + + unset($databaseId, $collectionId); + + $dbForProject->withTransaction(function () use ($dbForProject, $creates, $updates, $deletes) { + foreach ($creates as $databaseId => $collectionDocs) { + foreach ($collectionDocs as $collectionId => $docs) { + $dbForProject->createDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + } + } + foreach ($updates as $databaseId => $collectionDocs) { + foreach ($collectionDocs as $collectionId => $docs) { + $dbForProject->updateDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + } + } + foreach ($deletes as $databaseId => $collectionDocs) { + foreach ($collectionDocs as $collectionId => $docs) { + $dbForProject->deleteDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + } + } + }); + + $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committed', + ])); + + break; + + case 'rollback': + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc('$sequence'), + ]); + + $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'rolled_back', + 'rolledBackAt' => DateTime::now(), + 'reason' => $reason ?? 'user_request', + ])); + break; + } + + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->dynamic($transaction, Response::MODEL_TRANSACTION); + }); + App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->alias('/v1/database/collections/:collectionId/attributes/url') ->desc('Create URL attribute') From 705e5dd45bbf8c3292ca284abab2e7a19f3391f3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:03:58 -0400 Subject: [PATCH 044/385] Add txn support to existing routes --- app/controllers/api/databases.php | 601 +++++++++++++++++++++--------- 1 file changed, 422 insertions(+), 179 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 31b0585a7e..269018033a 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -13,6 +13,7 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Parameter; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Database\Validator\Operation; use Appwrite\Utopia\Database\Validator\Queries\Attributes; use Appwrite\Utopia\Database\Validator\Queries\Collections; use Appwrite\Utopia\Database\Validator\Queries\Databases; @@ -3492,17 +3493,18 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ] ) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.') + ->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) ->param('data', [], new JSON(), 'Document data as JSON object.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('response') ->inject('dbForProject') ->inject('user') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $permissions, ?array $documents, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function (string $databaseId, string $collectionId, ?string $documentId, string|array|null $data, ?array $permissions, ?array $documents, ?string $transactionId, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) { $data = \is_string($data) ? \json_decode($data, true) : $data; @@ -3565,6 +3567,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for collections with relationship attributes'); } + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + } + $setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) { $allowedPermissions = [ Database::PERMISSION_READ, @@ -3727,26 +3739,46 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') return $document; }, $documents); - try { - $dbForProject->createDocuments( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $documents - ); - } catch (DuplicateException) { - throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (NotFoundException) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } catch (RelationshipException $e) { - throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); - } catch (StructureException $e) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); - } + if (!empty($transactionId)) { + $operations = []; + foreach ($documents as $document) { + $operations[] = new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'create', + 'documentId' => $document->getId(), + 'data' => $document->getArrayCopy(), + ]); + } - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collection->getId()) - ->setContext('collection', $collection) - ->setContext('database', $database); + try { + $dbForProject->createDocuments('transactionLogs', $operations); + } catch (DuplicateException) { + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (NotFoundException) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } catch (RelationshipException $e) { + throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } + } else { + try { + $dbForProject->createDocuments( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $documents + ); + } catch (DuplicateException) { + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (NotFoundException) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } catch (RelationshipException $e) { + throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } + } // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { @@ -3786,12 +3818,20 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $processDocument($collection, $document); } + $response->setStatusCode(Response::STATUS_CODE_CREATED); + + if (empty($transactionId)) { + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collection->getId()) + ->setContext('collection', $collection) + ->setContext('database', $database); + } + $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection - $response->setStatusCode(Response::STATUS_CODE_CREATED); - if ($isBulk) { $response->dynamic(new Document([ 'total' => count($documents), @@ -3801,9 +3841,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') return; } - $queueForEvents - ->setParam('documentId', $documents[0]->getId()) - ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create'); + if (empty($transactionId)) { + $queueForEvents + ->setParam('documentId', $documents[0]->getId()) + ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create'); + } $response->dynamic( $documents[0], @@ -4244,12 +4286,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->param('documentId', '', new UID(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data) && \is_null($permissions)) { @@ -4269,6 +4312,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::COLLECTION_NOT_FOUND); } + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + } + // Read permission should not be required for update $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); if ($document->isEmpty()) { @@ -4388,20 +4441,31 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); - try { - $document = $dbForProject->updateDocument( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $document->getId(), - $newDocument - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (DuplicateException) { - throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (RelationshipException $e) { - throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); - } catch (StructureException $e) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + if (!empty($transactionId)) { + $dbForProject->createDocument('transactionLog', new Document([ + 'transactionInternalId' => $transaction->getSequence(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'documentId' => $document->getId(), + 'data' => $newDocument->getArrayCopy(), + 'action' => 'update', + ])); + } else { + try { + $document = $dbForProject->updateDocument( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $document->getId(), + $newDocument + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (DuplicateException) { + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (RelationshipException $e) { + throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } } // Add $collectionId and $databaseId for all documents @@ -4447,13 +4511,15 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ) ); - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collection->getId()) - ->setParam('documentId', $document->getId()) - ->setContext('collection', $collection) - ->setContext('database', $database) - ->setPayload($response->getPayload(), sensitive: $relationships); + if (empty($transactionId)) { + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $document->getId()) + ->setContext('collection', $collection) + ->setContext('database', $database) + ->setPayload($response->getPayload(), sensitive: $relationships); + } $response->dynamic($document, Response::MODEL_DOCUMENT); }); @@ -4488,12 +4554,13 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->param('documentId', '', new CustomId(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data) && \is_null($permissions)) { @@ -4513,6 +4580,16 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen throw new Exception(Exception::COLLECTION_NOT_FOUND); } + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + } + // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions, [ Database::PERMISSION_READ, @@ -4622,26 +4699,36 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); - $upserted = []; - try { - $modified = $dbForProject->createOrUpdateDocuments( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - [$newDocument], - onNext: function (Document $document) use (&$upserted) { - $upserted[] = $document; - }, - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (DuplicateException) { - throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (RelationshipException $e) { - throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); - } catch (StructureException $e) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + if (!empty($transactionId)) { + $dbForProject->createDocument('transactionLog', new Document([ + 'transactionInternalId' => $transaction->getSequence(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'action' => 'update', + 'data' => $newDocument->getArrayCopy(), + ])); + + $upserted = $newDocument; + } else { + try { + $dbForProject->createOrUpdateDocuments( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + [$newDocument], + onNext: function (Document $document) use (&$upserted) { + $upserted = $document; + }, + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (DuplicateException) { + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (RelationshipException $e) { + throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } } - $document = $upserted[0]; // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { $document->setAttribute('$databaseId', $database->getId()); @@ -4675,7 +4762,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen } }; - $processDocument($collection, $document); + $processDocument($collection, $upserted); $relationships = \array_map( fn ($document) => $document->getAttribute('key'), @@ -4685,15 +4772,17 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ) ); - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collection->getId()) - ->setParam('documentId', $document->getId()) - ->setContext('collection', $collection) - ->setContext('database', $database) - ->setPayload($response->getPayload(), sensitive: $relationships); + if (!empty($transactionId)) { + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $upserted->getId()) + ->setContext('collection', $collection) + ->setContext('database', $database) + ->setPayload($response->getPayload(), sensitive: $relationships); + } - $response->dynamic($document, Response::MODEL_DOCUMENT); + $response->dynamic($upserted, Response::MODEL_DOCUMENT); }); App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment') @@ -4727,11 +4816,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->param('attribute', '', new Key(), 'Attribute key.') ->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true) ->param('max', null, new Numeric(), 'Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -4742,33 +4832,60 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $document = $dbForProject->increaseDocumentAttribute( - collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - id: $documentId, - attribute: $attribute, - value: $value, - max: $max - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (NotFoundException) { - throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); - } catch (LimitException) { - 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'); + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + + $dbForProject->createDocument('transactionLog', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'increment', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'max' => $max, + ] + ])); + + $document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); + } else { + try { + $document = $dbForProject->increaseDocumentAttribute( + collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + id: $documentId, + attribute: $attribute, + value: $value, + max: $max + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); + } catch (LimitException) { + 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'); + } } $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collectionId) - ->setContext('collection', $collection) - ->setContext('database', $database); + if (!empty($transactionId)) { + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collectionId) + ->setContext('collection', $collection) + ->setContext('database', $database); + } $response->dynamic($document, Response::MODEL_DOCUMENT); }); @@ -4804,11 +4921,24 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->param('attribute', '', new Key(), 'Attribute key.') ->param('value', 1, new Numeric(), 'Value to decrement the attribute by. The value must be a number.', true) ->param('min', null, new Numeric(), 'Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function ( + string $databaseId, + string $collectionId, + string $documentId, + string $attribute, + int|float $value, + int|float|null $min, + ?string $transactionId, + Response $response, + Database $dbForProject, + Event $queueForEvents, + StatsUsage $queueForStatsUsage + ) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -4819,33 +4949,64 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $document = $dbForProject->decreaseDocumentAttribute( - collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - id: $documentId, - attribute: $attribute, - value: $value, - min: $min + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + + $dbForProject->createDocument('transactionLog', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'decrement', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'min' => $min, + ], + ])); + + // Fetch current document for response without mutating + $document = $dbForProject->getDocument( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $documentId ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (NotFoundException) { - throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); - } catch (LimitException) { - 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'); + } else { + try { + $document = $dbForProject->decreaseDocumentAttribute( + collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + id: $documentId, + attribute: $attribute, + value: $value, + min: $min + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); + } catch (LimitException) { + 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'); + } } $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collectionId) - ->setContext('collection', $collection) - ->setContext('database', $database); + if (empty($transactionId)) { + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collectionId) + ->setContext('collection', $collection) + ->setContext('database', $database); + } $response->dynamic($document, Response::MODEL_DOCUMENT); }); @@ -4878,12 +5039,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents') ->param('collectionId', '', new UID(), 'Collection ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { + ->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { $data = \is_string($data) ? \json_decode($data, true) : $data; @@ -4911,6 +5073,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for collections with relationship attributes'); } + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + } + try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -4987,11 +5159,12 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents') ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $databaseId, string $collectionId, array $documents, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { + ->action(function (string $databaseId, string $collectionId, array $documents, ?string $transactionId, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -5015,26 +5188,52 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents') $documents[$key] = new Document($document); } - $upserted = []; + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } - try { - $modified = $dbForProject->createOrUpdateDocuments( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $documents, - onNext: function (Document $document) use ($plan, &$upserted) { - if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) { - $upserted[] = $document; - } - }, - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (DuplicateException) { - throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (RelationshipException $e) { - throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); - } catch (StructureException $e) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + $operations = []; + foreach ($documents as $doc) { + $operations[] = new Document([ + 'transactionInternalId' => $transaction->getSequence(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'action' => 'upsert', + 'data' => $doc->getArrayCopy(), + ]); + } + + $dbForProject->createDocuments('transactionLogs', $operations); + + $modified = \count($documents); + $upserted = $documents; + } else { + $upserted = []; + + try { + $modified = $dbForProject->createOrUpdateDocuments( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $documents, + onNext: function (Document $document) use ($plan, &$upserted) { + if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) { + $upserted[] = $document; + } + }, + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (DuplicateException) { + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (RelationshipException $e) { + throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } } foreach ($upserted as $document) { @@ -5081,12 +5280,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('documentId', '', new UID(), 'Document ID.') + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.') ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->action(function (string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -5106,15 +5306,33 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::DOCUMENT_NOT_FOUND); } - try { - $dbForProject->deleteDocument( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $documentId - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (RestrictedException) { - throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED); + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + + $dbForProject->createDocument('transactionLog', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $document->getId(), + 'action' => 'delete', + ])); + } else { + try { + $dbForProject->deleteDocument( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $documentId + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (RestrictedException) { + throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED); + } } $operations = 0; @@ -5160,21 +5378,23 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); - $relationships = \array_map( - fn ($document) => $document->getAttribute('key'), - \array_filter( - $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP - ) - ); + if (!empty($transactionId)) { + $relationships = \array_map( + fn ($document) => $document->getAttribute('key'), + \array_filter( + $collection->getAttribute('attributes', []), + fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + ) + ); - $queueForEvents - ->setParam('databaseId', $databaseId) - ->setParam('collectionId', $collection->getId()) - ->setParam('documentId', $document->getId()) - ->setContext('collection', $collection) - ->setContext('database', $database) - ->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships); + $queueForEvents + ->setParam('databaseId', $databaseId) + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $document->getId()) + ->setContext('collection', $collection) + ->setContext('database', $database) + ->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships); + } $response->noContent(); }); @@ -5206,12 +5426,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents') ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { + ->action(function (string $databaseId, string $collectionId, array $queries, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -5231,6 +5452,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for collections with relationship attributes'); } + if (!empty($transactionId)) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status') !== 'pending') { + throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); + } + } + try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -5239,20 +5470,32 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = []; - try { - $modified = $dbForProject->deleteDocuments( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $queries, - onNext: function (Document $document) use ($plan, &$documents) { - if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) { - $documents[] = $document; - } - }, - ); - } catch (ConflictException) { - throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); - } catch (RestrictedException) { - throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED); + if (!empty($transactionId)) { + $dbForProject->createDocument('transactionLog', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'delete', + 'data' => [$queries], + ])); + + $modified = 0; + } else { + try { + $modified = $dbForProject->deleteDocuments( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $queries, + onNext: function (Document $document) use ($plan, &$documents) { + if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) { + $documents[] = $document; + } + }, + ); + } catch (ConflictException) { + throw new Exception(Exception::DOCUMENT_UPDATE_CONFLICT); + } catch (RestrictedException) { + throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED); + } } foreach ($documents as $document) { From b71688a587e62f71da6f845f967fd139f7266114 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:07:14 -0400 Subject: [PATCH 045/385] Format --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 269018033a..534b2fbe04 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -5199,7 +5199,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents') $operations = []; foreach ($documents as $doc) { - $operations[] = new Document([ + $operations[] = new Document([ 'transactionInternalId' => $transaction->getSequence(), 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), From d13081b4f29c49ad8ae418b81605d67275b8181a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:45:07 -0400 Subject: [PATCH 046/385] Use const --- app/controllers/api/databases.php | 2 +- app/init/constants.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 534b2fbe04..9175b53f19 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1556,7 +1556,7 @@ App::post('/v1/databases/transactions') ], contentType: ContentType::JSON )) - ->param('ttl', 300, new Integer(), 'Seconds before the transaction expires.', true) + ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) ->inject('response') ->inject('dbForProject') ->action(function (int $ttl, Response $response, Database $dbForProject) { diff --git a/app/init/constants.php b/app/init/constants.php index ebf79086a7..96606ff782 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -52,6 +52,9 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes const APP_DATABASE_QUERY_MAX_VALUES = 500; const APP_DATABASE_ENCRYPT_SIZE_MIN = 150; +const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute +const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour +const APP_DATABASE_TXN_TTL_DEFAULT = 300; // 5 minutes const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_SITES = '/storage/sites'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; From 4da95f765b44b5b9592e5152d4fb7e7783c38b72 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:45:14 -0400 Subject: [PATCH 047/385] Don't require data --- src/Appwrite/Utopia/Database/Validator/Operation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 3f9a15673a..e46862bdb2 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -38,7 +38,7 @@ class Operation extends Validator } // Mandatory keys - $required = ['databaseId', 'collectionId', 'action', 'payload']; + $required = ['databaseId', 'collectionId', 'action']; foreach ($required as $key) { if (!\array_key_exists($key, $value)) { $this->description = "Missing required key: {$key}"; From bd9d827a16b0e665ee7faeca7a52c7d7c176f46b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:54:32 -0400 Subject: [PATCH 048/385] Add tests --- .../e2e/Services/Databases/DatabasesBase.php | 593 ++++++++++++++++-- 1 file changed, 555 insertions(+), 38 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index e34691839d..898996733c 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -3891,28 +3891,47 @@ trait DatabasesBase $this->assertCount(1, $documentsUser2['body']['documents']); } - /** - * @depends testDefaultPermissions - */ - public function testUniqueIndexDuplicate(array $data): array + public function testUniqueIndexDuplicate(): void { - $databaseId = $data['databaseId']; - $uniqueIndex = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + // Setup: create database, collection, attribute, and initial document + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'unique_title', - 'type' => 'unique', - 'attributes' => ['title'], + 'databaseId' => ID::unique(), + 'name' => 'Unique Index DB', ]); - - $this->assertEquals(202, $uniqueIndex['headers']['status-code']); - + $databaseId = $database['body']['$id']; + $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Movies', + 'permissions' => [ + Permission::create(Role::user(ID::custom($this->getUser()['$id']))), + Permission::read(Role::user(ID::custom($this->getUser()['$id']))), + Permission::update(Role::user(ID::custom($this->getUser()['$id']))), + Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), + ], + 'documentSecurity' => true, + ]); + $moviesId = $movies['body']['$id']; + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); sleep(2); - - // test for failure - $duplicate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + // Insert initial document + $doc1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -3931,11 +3950,45 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); + $this->assertEquals(201, $doc1['headers']['status-code']); + // Create unique index + $uniqueIndex = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'unique_title', + 'type' => 'unique', + 'attributes' => ['title'], + ]); + $this->assertEquals(202, $uniqueIndex['headers']['status-code']); + sleep(2); + + // test for failure (duplicate title) + $duplicate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'title' => 'Captain America', + 'releaseYear' => 1944, + 'actors' => [ + 'Chris Evans', + 'Samuel Jackson', + ] + ], + 'permissions' => [ + Permission::read(Role::user(ID::custom($this->getUser()['$id']))), + Permission::update(Role::user(ID::custom($this->getUser()['$id']))), + Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), + ] + ]); $this->assertEquals(409, $duplicate['headers']['status-code']); // Test for exception when updating document to conflict - $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -3954,11 +4007,9 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); - $this->assertEquals(201, $document['headers']['status-code']); - // Test for exception when updating document to conflict - $duplicate = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $document['body']['$id'], array_merge([ + $duplicate = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $document['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -3977,17 +4028,48 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); - $this->assertEquals(409, $duplicate['headers']['status-code']); - - return $data; } - /** - * @depends testUniqueIndexDuplicate - */ - public function testPersistantCreatedAt(array $data): array + public function testPersistentCreatedAt(): void { + // Setup: create database, collection, attribute + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'CreatedAtDB', + ]); + $databaseId = $database['body']['$id']; + $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Movies', + 'permissions' => [ + Permission::create(Role::user(ID::custom($this->getUser()['$id']))), + Permission::read(Role::user(ID::custom($this->getUser()['$id']))), + Permission::update(Role::user(ID::custom($this->getUser()['$id']))), + Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), + ], + 'documentSecurity' => true, + ]); + $moviesId = $movies['body']['$id']; + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); + sleep(2); $headers = $this->getSide() === 'client' ? array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -3997,52 +4079,43 @@ trait DatabasesBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]; - $document = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents', $headers, [ + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', $headers, [ 'documentId' => ID::unique(), 'data' => [ 'title' => 'Creation Date Test', 'releaseYear' => 2000 ] ]); - $this->assertEquals($document['body']['title'], 'Creation Date Test'); - $documentId = $document['body']['$id']; $createdAt = $document['body']['$createdAt']; $updatedAt = $document['body']['$updatedAt']; \sleep(1); - - $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, $headers, [ + $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $documentId, $headers, [ 'data' => [ 'title' => 'Updated Date Test', ] ]); - $updatedAtSecond = $document['body']['$updatedAt']; - $this->assertEquals($document['body']['title'], 'Updated Date Test'); $this->assertEquals($document['body']['$createdAt'], $createdAt); $this->assertNotEquals($document['body']['$updatedAt'], $updatedAt); \sleep(1); - - $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, $headers, [ + $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $documentId, $headers, [ 'data' => [ 'title' => 'Again Updated Date Test', '$createdAt' => '2022-08-01 13:09:23.040', // $createdAt is not updatable '$updatedAt' => '2022-08-01 13:09:23.050' // system will update it not api ] ]); - $this->assertEquals($document['body']['title'], 'Again Updated Date Test'); $this->assertEquals($document['body']['$createdAt'], $createdAt); $this->assertNotEquals($document['body']['$createdAt'], '2022-08-01 13:09:23.040'); $this->assertNotEquals($document['body']['$updatedAt'], $updatedAt); $this->assertNotEquals($document['body']['$updatedAt'], $updatedAtSecond); $this->assertNotEquals($document['body']['$updatedAt'], '2022-08-01 13:09:23.050'); - - return $data; } public function testUpdatePermissionsWithEmptyPayload(): array @@ -5506,5 +5579,449 @@ trait DatabasesBase $this->assertEquals(400, $typeErr['headers']['status-code']); } + public function testCreateTransaction(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + $databaseId = $database['body']['$id']; + + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 900 + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertArrayHasKey('transactionId', $response['body']); + $this->assertEquals('pending', $response['body']['status']); + $this->assertArrayHasKey('expiresAt', $response['body']); + $this->assertGreaterThanOrEqual(DateTime::addSeconds(new \DateTime(), 800), strtotime($response['body']['expiresAt'])); + $this->assertLessThanOrEqual(DateTime::addSeconds(new \DateTime(), 1000), strtotime($response['body']['expiresAt'])); + + // Failure: invalid TTL + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => -1 + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 1000000 + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + public function testAddOperationsToTransaction(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $transaction = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 300 + ]); + + $transactionId = $transaction['body']['transactionId']; + + // Add operations + $transaction = $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => [ + 'name' => 'Tester 1' + ], + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => [ + 'name' => 'Tester 2' + ], + ] + ] + ]); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $this->assertNotEmpty($transaction['body']); + + // Failure cases + + // Invalid databaseId + $response = $this->client->call(Client::METHOD_POST, "/databases/invalidDatabaseId/transactions/$transactionId/operations", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'operations' => [ + [ + 'databaseId' => 'invalidDatabaseId', + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => ['name' => 'Invalid Tester'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Invalid collectionId + $response = $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => 'invalidCollectionId', + 'action' => 'create', + 'data' => ['name' => 'Invalid Tester'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Invalid action + $response = $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'invalidAction', + 'data' => ['name' => 'Invalid Tester'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Missing required fields + $response = $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId + // Missing action + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + public function testCommitTransaction(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionTestCommit', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + \sleep(2); + + $transaction = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['transactionId']; + + $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => ['name' => 'Tester'], + ] + ] + ]); + + $transaction = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/transactions/$transactionId", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(200, $transaction['headers']['status-code']); + $this->assertEquals('committed', $transaction['body']['status']); + + $documents = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/collections/$collectionId/documents", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(1, count($documents['body']['documents'])); + $this->assertEquals('Tester', $documents['body']['documents'][0]['name']); + } + + public function testRollbackTransaction(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionTestRollback', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + \sleep(2); + + $transaction = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['transactionId']; + + $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => [ + 'name' => 'value' + ], + ] + ] + ]); + + $rollback = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/transactions/$transactionId", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $rollback['headers']['status-code']); + $this->assertEquals('rolledBack', $rollback['body']['status']); + + $documents = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/collections/$collectionId/documents", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()); + + $this->assertEquals(0, count($documents['body']['documents'])); + } + public function testTransactionConflict(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestConflictDatabase' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionTestConflict', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + // Add string attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], $this->getHeaders()), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $transaction = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/transactions', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $transactionId = $transaction['body']['transactionId']; + + $this->client->call(Client::METHOD_POST, "/databases/$databaseId/transactions/$transactionId/operations", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => ['attribute' => 'value'], + ] + ] + ]); + + // Commit the transaction + $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/transactions/$transactionId", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + // Attempt to commit again, should fail with a conflict + $conflictResponse = $this->client->call(Client::METHOD_PATCH, "/databases/$databaseId/transactions/$transactionId", \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(409, $conflictResponse['headers']['status-code']); + } } From 145cb54bc034655bdeb912188d3387eab20a9761 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 16:58:25 -0400 Subject: [PATCH 049/385] Separate bulk --- app/config/collections/projects.php | 4 +-- app/controllers/api/databases.php | 30 ++++++++++++------- .../Utopia/Database/Validator/Operation.php | 26 ++++++++++------ src/Appwrite/Utopia/Response.php | 2 ++ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 97cde969de..7a1465fb92 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2651,9 +2651,9 @@ return [ 'orders' => [], ], [ - '$id' => ID::custom('_key_db_coll'), + '$id' => ID::custom('_key_internal_path'), 'type' => Database::INDEX_KEY, - 'attributes' => ['databaseId', 'collectionId'], + 'attributes' => ['databaseInternalId', 'collectionInternalId'], 'lengths' => [], 'orders' => [], ], diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9175b53f19..0159b199c7 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -4442,10 +4442,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLog', new Document([ - 'transactionInternalId' => $transaction->getSequence(), + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), 'documentId' => $document->getId(), 'data' => $newDocument->getArrayCopy(), 'action' => 'update', @@ -4700,10 +4700,10 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLog', new Document([ - 'transactionInternalId' => $transaction->getSequence(), + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), 'action' => 'update', 'data' => $newDocument->getArrayCopy(), ])); @@ -4841,7 +4841,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLog', new Document([ + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), @@ -4958,7 +4958,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLog', new Document([ + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), @@ -5098,6 +5098,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = []; + if (!empty($transactionId)) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'bulkUpdate', + 'data' => \compact('data', 'queries'), + ])); + } + try { $modified = $dbForProject->updateDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), @@ -5315,7 +5325,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLog', new Document([ + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), @@ -5471,12 +5481,12 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = []; if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLog', new Document([ + $dbForProject->createDocument('transactionLogs', new Document([ 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'action' => 'delete', - 'data' => [$queries], + 'action' => 'bulkDelete', + 'data' => ['queries' => $queries], ])); $modified = 0; diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index e46862bdb2..45122df800 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -8,12 +8,21 @@ class Operation extends Validator { private string $description = ''; - /** @var string[] */ + /** @var array */ + private array $required = [ + 'databaseId', + 'collectionId', + 'action', + ]; + + /** @var array */ private array $actions = [ 'create', 'update', + 'bulkUpdate', 'upsert', - 'delete' + 'delete', + 'bulkDelete', ]; public function getDescription(): string @@ -38,16 +47,15 @@ class Operation extends Validator } // Mandatory keys - $required = ['databaseId', 'collectionId', 'action']; - foreach ($required as $key) { + foreach ($this->required as $key) { if (!\array_key_exists($key, $value)) { $this->description = "Missing required key: {$key}"; return false; } } - // databaseId / collectionId / action must be non‑empty strings - foreach (['databaseId', 'collectionId', 'action'] as $key) { + // Required keys must be non‑empty + foreach ($this->required as $key) { if (!\is_string($value[$key]) || \trim($value[$key]) === '') { $this->description = "Key '{$key}' must be a non‑empty string"; return false; @@ -60,9 +68,9 @@ class Operation extends Validator return false; } - // Payload must be array (can be empty) - if (!\is_array($value['payload'])) { - $this->description = "Key 'payload' must be an array"; + // Data must be array (can be empty) + if (!\is_array($value['data'])) { + $this->description = "Key 'data' must be an array"; return false; } diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index a376dd9cb6..994f0e2e73 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -105,6 +105,7 @@ use Appwrite\Utopia\Response\Model\TemplateSMS; use Appwrite\Utopia\Response\Model\TemplateVariable; use Appwrite\Utopia\Response\Model\Token; use Appwrite\Utopia\Response\Model\Topic; +use Appwrite\Utopia\Response\Model\Transaction; use Appwrite\Utopia\Response\Model\UsageBuckets; use Appwrite\Utopia\Response\Model\UsageCollection; use Appwrite\Utopia\Response\Model\UsageDatabase; @@ -515,6 +516,7 @@ class Response extends SwooleResponse ->setModel(new TemplateVariable()) ->setModel(new Token()) ->setModel(new Topic()) + ->setModel(new Transaction()) ->setModel(new UsageBuckets()) ->setModel(new UsageCollection()) ->setModel(new UsageDatabase()) From 0b00ce28e0c241cba4642abf00905551c4c34010 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 19:37:53 -0400 Subject: [PATCH 050/385] Remove redundant attributes --- app/config/collections/projects.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 7a1465fb92..e76a17abe5 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2537,26 +2537,6 @@ return [ 'array' => false, 'filters' => ['datetime'], ], - [ - '$id' => ID::custom('committedAt'), - 'type' => Database::VAR_DATETIME, - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('rolledBackAt'), - 'type' => Database::VAR_DATETIME, - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], ], 'indexes' => [ [ From 1e82c7f0ef37d4e9b6a37ee1126757a26b9a6036 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 21:54:30 -0400 Subject: [PATCH 051/385] Add listTransactions --- app/controllers/api/databases.php | 35 +++++++++++++++++++ .../Utopia/Database/Validator/Operation.php | 2 +- .../Validator/Queries/Transactions.php | 16 +++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 0159b199c7..9819377f9d 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1621,6 +1621,41 @@ App::post('/v1/databases/transactions/:transactionId/operations') ->dynamic($transaction, Response::MODEL_TRANSACTION); }); +App::get('/v1/databases/transactions') + ->desc('List transactions') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'listTransactions', + description: '/docs/references/databases/list-transactions.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_TRANSACTION_LIST, + ) + ], + contentType: ContentType::JSON + )) + ->param('queries', [], new Transactions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries).', true) + ->inject('response') + ->inject('dbForProject') + ->action(function (array $queries, Response $response, Database $dbForProject) { + try { + $queries = Query::parseQueries($queries); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + + $response->dynamic(new Document([ + 'transactions' => $dbForProject->find('transactions', $queries), + 'total' => $dbForProject->count('transactions', $queries), + ]), Response::MODEL_TRANSACTION_LIST); + }); + App::get('/v1/databases/transactions/:transactionId') ->desc('Get transaction') ->groups(['api', 'database', 'transactions']) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 45122df800..989fd76eec 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -19,9 +19,9 @@ class Operation extends Validator private array $actions = [ 'create', 'update', - 'bulkUpdate', 'upsert', 'delete', + 'bulkUpdate', 'bulkDelete', ]; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php new file mode 100644 index 0000000000..ab3e933d6f --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php @@ -0,0 +1,16 @@ + Date: Tue, 17 Jun 2025 21:54:40 -0400 Subject: [PATCH 052/385] Update params --- app/controllers/api/databases.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9819377f9d..69537f434f 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1691,7 +1691,7 @@ App::get('/v1/databases/transactions/:transactionId') }); App::patch('/v1/databases/transactions/:transactionId') - ->desc('Update transaction (commit / rollback)') + ->desc('Update transaction') ->groups(['api', 'database', 'transactions']) ->label('scope', 'collections.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) @@ -1710,11 +1710,20 @@ App::patch('/v1/databases/transactions/:transactionId') contentType: ContentType::JSON )) ->param('transactionId', '', new UID(), 'Transaction ID.') - ->param('action', '', new WhiteList(['commit','rollback']), 'Action to take, commit or rollback.') + ->param('commit', false, new Boolean(), 'Commit transaction?', true) + ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) + ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') ->inject('project') - ->action(function (string $transactionId, string $action, ?string $reason, Response $response, Database $dbForProject, Document $project) { + ->action(function (string $transactionId, bool $commit, bool $rollback, ?\DateTime $requestTimestamp, ?string $reason, Response $response, Database $dbForProject, Document $project) { + if (!$commit && !$rollback) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); + } + if ($commit && $rollback) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot commit and rollback at the same time'); + } + $transaction = $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { From 5d5a6fbc5d89d38e2e7a9508bed186dc0adfd9f5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 21:55:08 -0400 Subject: [PATCH 053/385] Add delete transaction --- app/controllers/api/databases.php | 205 +++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 61 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 69537f434f..9f6cfa1aec 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1734,79 +1734,122 @@ App::patch('/v1/databases/transactions/:transactionId') throw new Exception(Exception::TRANSACTION_NOT_READY); } - switch ($action) { - case 'commit': - // Get staged operations - $operations = $dbForProject->find('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc('$sequence'), - ]); + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } - $creates = $updates = $deletes = []; + if ($commit) { + $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committing', + ])); - foreach ($operations as $operation) { - $databaseId = $operation['databaseInternalId']; - $collectionId = $operation['collectionInternalId']; - $documentId = $operation['documentInternalId']; + $operations = $dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); - switch ($operation['action']) { - case 'create': - $creates[$databaseId][$collectionId][] = new Document([ - '$id' => $documentId ?? ID::unique(), - ...$operation['data'] - ]); - break; - case 'update': - case 'upsert': - $updates[$databaseId][$collectionId][] = new Document([ - '$id' => $documentId, - ...$operation['data'], - ]); - break; - case 'delete': - $deletes[$databaseId][$collectionId][] = $documentId; - break; - } - } + $creates = $updates = $deletes = $bulkUpdates = $bulkDeletes = []; - unset($databaseId, $collectionId); + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $documentId = $operation['documentId']; - $dbForProject->withTransaction(function () use ($dbForProject, $creates, $updates, $deletes) { - foreach ($creates as $databaseId => $collectionDocs) { - foreach ($collectionDocs as $collectionId => $docs) { - $dbForProject->createDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + switch ($operation['action']) { + case 'create': + $creates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId ?? ID::unique(), + ...$operation['data'] + ]); + break; + case 'update': + case 'upsert': + $updates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId, + ...$operation['data'], + ]); + break; + case 'delete': + $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; + break; + case 'bulkUpdate': + $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ + 'data' => $operation['data'] ?? null, + 'queries' => $operation['queries'] ?? [], + ]; + break; + case 'bulkDelete': + $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ + 'queries' => $operation['queries'] ?? [], + ]; + break; } } - foreach ($updates as $databaseId => $collectionDocs) { - foreach ($collectionDocs as $collectionId => $docs) { - $dbForProject->updateDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + + try { + foreach ($creates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createDocuments("database_{$dbId}_collection_{$colId}", $docs); + } } - } - foreach ($deletes as $databaseId => $collectionDocs) { - foreach ($collectionDocs as $collectionId => $docs) { - $dbForProject->deleteDocuments("database_{$databaseId}_collection_{$collectionId}", $docs); + foreach ($updates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createOrUpdateDocuments("database_{$dbId}_collection_{$colId}", $docs); + } } + foreach ($deletes as $dbId => $cols) { + foreach ($cols as $colId => $ids) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", [ + Query::equal('$id', $ids), + ]); + } + } + foreach ($bulkUpdates as $dbId => $cols) { + foreach ($cols as $colId => $updates) { + foreach ($updates as $update) { + $dbForProject->updateDocuments("database_{$dbId}_collection_{$colId}", $update['data'], $update['queries']); + } + } + } + foreach ($bulkDeletes as $dbId => $cols) { + foreach ($cols as $colId => $deletes) { + foreach ($deletes as $delete) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", $delete['queries']); + } + } + } + + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committed', + ])); + + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + } catch (DuplicateException|ConflictException) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + + throw new Exception(Exception::TRANSACTION_CONFLICT); } }); + }); - $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'committed', - ])); + $transaction = $dbForProject->getDocument('transactions', $transactionId); + } - break; + if ($rollback) { + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); - case 'rollback': - $dbForProject->deleteDocuments('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc('$sequence'), - ]); - - $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'rolled_back', - 'rolledBackAt' => DateTime::now(), - 'reason' => $reason ?? 'user_request', - ])); - break; + $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'rolledBack', + ])); } $response @@ -1814,6 +1857,46 @@ App::patch('/v1/databases/transactions/:transactionId') ->dynamic($transaction, Response::MODEL_TRANSACTION); }); +App::delete('/v1/databases/transactions/:transactionId') + ->desc('Delete transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'deleteTransaction', + description: '/docs/references/databases/delete-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_NOCONTENT, + model: Response::MODEL_NONE, + ) + ], + contentType: ContentType::NONE + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->action(function (string $transactionId, Response $response, Database $dbForProject) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + + if (!$dbForProject->deleteDocument('transactions', $transactionId)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove transaction from DB'); + } + + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $response->noContent(); + }); + App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->alias('/v1/database/collections/:collectionId/attributes/url') ->desc('Create URL attribute') @@ -3790,8 +3873,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'action' => 'create', 'documentId' => $document->getId(), + 'action' => 'create', 'data' => $document->getArrayCopy(), ]); } @@ -4491,8 +4574,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), 'documentId' => $document->getId(), - 'data' => $newDocument->getArrayCopy(), 'action' => 'update', + 'data' => $newDocument->getArrayCopy(), ])); } else { try { From 75469eed7d6636d93b141e630575ad8336f2106a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 21:55:38 -0400 Subject: [PATCH 054/385] Fix staged values --- app/controllers/api/databases.php | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9f6cfa1aec..cc51fd0def 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -18,6 +18,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Attributes; use Appwrite\Utopia\Database\Validator\Queries\Collections; use Appwrite\Utopia\Database\Validator\Queries\Databases; use Appwrite\Utopia\Database\Validator\Queries\Indexes; +use Appwrite\Utopia\Database\Validator\Queries\Transactions; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; @@ -1594,23 +1595,35 @@ App::post('/v1/databases/transactions/:transactionId/operations') ->param('operations', [], new ArrayList(new Operation()), 'Array of staged operations.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $transactionId, array $operations, Response $response, Database $dbForProject) { + ->inject('plan') + ->action(function (string $transactionId, array $operations, Response $response, Database $dbForProject, array $plan) { $transaction = $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty() || $transaction['status'] !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } - $staged = []; - foreach ($operations as $op) { + + $databases = $collections = $staged = []; + foreach ($operations as $operation) { + $database = $databases[$operation['databaseId']] ??= $dbForProject->getDocument('databases', $operation['databaseId']); + if ($database->isEmpty()) { + throw new Exception(Exception::DATABASE_NOT_FOUND); + } + + $collection = $collections[$operation['collectionId']] ??= $dbForProject->getDocument('database_' . $database->getSequence(), $operation['collectionId']); + if ($collection->isEmpty()) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } + $staged[] = new Document([ '$id' => ID::unique(), - 'transactionId' => $transactionId, - 'databaseId' => $op['databaseId'] ?? null, - 'collectionId' => $op['collectionId'] ?? null, - 'documentId' => $op['documentId'] ?? null, - 'action' => $op['action'], - 'data' => $op['data'] ?? [], + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $operation['documentId'] ?? ID::unique(), + 'action' => $operation['action'], + 'data' => $operation['data'] ?? new \stdClass(), ]); } From 910f27016bfb4c3fddd36b06e5f48e7dd6b0ff6c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Jun 2025 22:08:08 -0400 Subject: [PATCH 055/385] Add operation to txn meta --- app/config/collections/projects.php | 10 ++++++++++ app/controllers/api/databases.php | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index e76a17abe5..53a665bfab 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2527,6 +2527,16 @@ return [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('operations'), + 'type' => Database::VAR_INTEGER, + 'size' => 0, + 'signed' => false, + 'required' => true, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('expiresAt'), 'type' => Database::VAR_DATETIME, diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index cc51fd0def..3e0ec7a88b 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1564,6 +1564,7 @@ App::post('/v1/databases/transactions') $transaction = $dbForProject->createDocument('transactions', new Document([ '$id' => ID::unique(), 'status' => 'pending', + 'operations' => 0, 'expiresAt' => DateTime::addSeconds(new \DateTime(), $ttl), ])); @@ -1598,11 +1599,19 @@ App::post('/v1/databases/transactions/:transactionId/operations') ->inject('plan') ->action(function (string $transactionId, array $operations, Response $response, Database $dbForProject, array $plan) { $transaction = $dbForProject->getDocument('transactions', $transactionId); - - if ($transaction->isEmpty() || $transaction['status'] !== 'pending') { + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + + if (($existing + \count($operations)) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding ' . \count($operations) . ' would exceed the maximum of ' . $maxBatch + ); + } $databases = $collections = $staged = []; foreach ($operations as $operation) { @@ -1627,7 +1636,12 @@ App::post('/v1/databases/transactions/:transactionId/operations') ]); } - $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { + $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'operations' => $existing + \count($operations), + ])); + }); $response ->setStatusCode(Response::STATUS_CODE_CREATED) From b471bd0f581ed06ab886a664d133c8017d141973 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 18 Jun 2025 16:53:47 -0400 Subject: [PATCH 056/385] Add operations to response model --- app/controllers/api/databases.php | 182 +++++++++--------- .../Utopia/Response/Model/Transaction.php | 6 + 2 files changed, 95 insertions(+), 93 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 3e0ec7a88b..5b9d571211 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1752,118 +1752,114 @@ App::patch('/v1/databases/transactions/:transactionId') } $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } - if ($transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::TRANSACTION_NOT_READY); } $now = new \DateTime(); - $expiresAt = new \DateTime($transaction->getAttribute('expiresAt')); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); if ($now > $expiresAt) { throw new Exception(Exception::TRANSACTION_EXPIRED); } if ($commit) { - $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committing', + ])); + + $operations = $dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $creates = $updates = $deletes = $bulkUpdates = $bulkDeletes = []; + + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $documentId = $operation['documentId']; + + switch ($operation['action']) { + case 'create': + $creates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId ?? ID::unique(), + ...$operation['data'] + ]); + break; + case 'update': + case 'upsert': + $updates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId, + ...$operation['data'], + ]); + break; + case 'delete': + $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; + break; + case 'bulkUpdate': + $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ + 'data' => $operation['data'] ?? null, + 'queries' => $operation['queries'] ?? [], + ]; + break; + case 'bulkDelete': + $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ + 'queries' => $operation['queries'] ?? [], + ]; + break; + } + } + + try { + foreach ($creates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createDocuments("database_{$dbId}_collection_{$colId}", $docs); + } + } + foreach ($updates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createOrUpdateDocuments("database_{$dbId}_collection_{$colId}", $docs); + } + } + foreach ($deletes as $dbId => $cols) { + foreach ($cols as $colId => $ids) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", [ + Query::equal('$id', $ids), + ]); + } + } + foreach ($bulkUpdates as $dbId => $cols) { + foreach ($cols as $colId => $updates) { + foreach ($updates as $update) { + $dbForProject->updateDocuments("database_{$dbId}_collection_{$colId}", $update['data'], $update['queries']); + } + } + } + foreach ($bulkDeletes as $dbId => $cols) { + foreach ($cols as $colId => $deletes) { + foreach ($deletes as $delete) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", $delete['queries']); + } + } + } + $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'committing', + 'status' => 'committed', ])); - $operations = $dbForProject->find('transactionLogs', [ + $dbForProject->deleteDocuments('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), ]); + } catch (DuplicateException|ConflictException) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); - $creates = $updates = $deletes = $bulkUpdates = $bulkDeletes = []; - - foreach ($operations as $operation) { - $databaseInternalId = $operation['databaseInternalId']; - $collectionInternalId = $operation['collectionInternalId']; - $documentId = $operation['documentId']; - - switch ($operation['action']) { - case 'create': - $creates[$databaseInternalId][$collectionInternalId][] = new Document([ - '$id' => $documentId ?? ID::unique(), - ...$operation['data'] - ]); - break; - case 'update': - case 'upsert': - $updates[$databaseInternalId][$collectionInternalId][] = new Document([ - '$id' => $documentId, - ...$operation['data'], - ]); - break; - case 'delete': - $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; - break; - case 'bulkUpdate': - $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ - 'data' => $operation['data'] ?? null, - 'queries' => $operation['queries'] ?? [], - ]; - break; - case 'bulkDelete': - $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ - 'queries' => $operation['queries'] ?? [], - ]; - break; - } - } - - try { - foreach ($creates as $dbId => $cols) { - foreach ($cols as $colId => $docs) { - $dbForProject->createDocuments("database_{$dbId}_collection_{$colId}", $docs); - } - } - foreach ($updates as $dbId => $cols) { - foreach ($cols as $colId => $docs) { - $dbForProject->createOrUpdateDocuments("database_{$dbId}_collection_{$colId}", $docs); - } - } - foreach ($deletes as $dbId => $cols) { - foreach ($cols as $colId => $ids) { - $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", [ - Query::equal('$id', $ids), - ]); - } - } - foreach ($bulkUpdates as $dbId => $cols) { - foreach ($cols as $colId => $updates) { - foreach ($updates as $update) { - $dbForProject->updateDocuments("database_{$dbId}_collection_{$colId}", $update['data'], $update['queries']); - } - } - } - foreach ($bulkDeletes as $dbId => $cols) { - foreach ($cols as $colId => $deletes) { - foreach ($deletes as $delete) { - $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", $delete['queries']); - } - } - } - - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'committed', - ])); - - $dbForProject->deleteDocuments('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - ]); - } catch (DuplicateException|ConflictException) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - - throw new Exception(Exception::TRANSACTION_CONFLICT); - } - }); + throw new Exception(Exception::TRANSACTION_CONFLICT); + } }); $transaction = $dbForProject->getDocument('transactions', $transactionId); diff --git a/src/Appwrite/Utopia/Response/Model/Transaction.php b/src/Appwrite/Utopia/Response/Model/Transaction.php index c6eafd18b9..aae2a9b572 100644 --- a/src/Appwrite/Utopia/Response/Model/Transaction.php +++ b/src/Appwrite/Utopia/Response/Model/Transaction.php @@ -34,6 +34,12 @@ class Transaction extends Model 'default' => 'pending', 'example' => 'pending', ]) + ->addRule('operations', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of operations in the transaction.', + 'default' => 0, + 'example' => 5, + ]) ->addRule('expiresAt', [ 'type' => self::TYPE_DATETIME, 'description' => 'Expiration time in ISO 8601 format.', From ac329479085a94963e93ed45774ed5e42f0a6d79 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 18 Jun 2025 19:17:47 -0400 Subject: [PATCH 057/385] Update db --- composer.json | 2 +- composer.lock | 39 ++++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 18507e04f1..2529dc404d 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.71.*", + "utopia-php/database": "dev-feat-transaction-pinning as 0.71.6", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dsn": "0.2.1", diff --git a/composer.lock b/composer.lock index ff6884de7d..aee5501bc8 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": "1557e469b3074a6478a0b2fd522e1a2a", + "content-hash": "aabb75770fc1377ff5188957653cbdea", "packages": [ { "name": "adhocore/jwt", @@ -3490,16 +3490,16 @@ }, { "name": "utopia-php/database", - "version": "0.71.6", + "version": "dev-feat-transaction-pinning", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2bd87acc40af087fc0fdcccc47c43141dff0be5c" + "reference": "8c764ed339caa60687b2362a96712bb6cbabf1eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2bd87acc40af087fc0fdcccc47c43141dff0be5c", - "reference": "2bd87acc40af087fc0fdcccc47c43141dff0be5c", + "url": "https://api.github.com/repos/utopia-php/database/zipball/8c764ed339caa60687b2362a96712bb6cbabf1eb", + "reference": "8c764ed339caa60687b2362a96712bb6cbabf1eb", "shasum": "" }, "require": { @@ -3540,9 +3540,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.71.6" + "source": "https://github.com/utopia-php/database/tree/feat-transaction-pinning" }, - "time": "2025-06-16T16:48:37+00:00" + "time": "2025-06-18T20:49:54+00:00" }, { "name": "utopia-php/detector", @@ -4807,16 +4807,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.41.7", + "version": "0.41.8", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "d8c7bb26ea32ab378faf4e0dfa62fd15fe37c57b" + "reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d8c7bb26ea32ab378faf4e0dfa62fd15fe37c57b", - "reference": "d8c7bb26ea32ab378faf4e0dfa62fd15fe37c57b", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/93ffb24b25b376ca4423e3a5caf6f916673af4b2", + "reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2", "shasum": "" }, "require": { @@ -4852,9 +4852,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.41.7" + "source": "https://github.com/appwrite/sdk-generator/tree/0.41.8" }, - "time": "2025-06-13T17:05:57+00:00" + "time": "2025-06-18T13:20:45+00:00" }, { "name": "doctrine/annotations", @@ -8231,9 +8231,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-transaction-pinning", + "alias": "0.71.6", + "alias_normalized": "0.71.6.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 2745d8dce7edf4e614aee82d5250557ee44a5f61 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 18 Jun 2025 19:51:37 -0400 Subject: [PATCH 058/385] Add single op counting --- app/controllers/api/databases.php | 268 ++++++++++++++++++++++-------- 1 file changed, 195 insertions(+), 73 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 5b9d571211..26fe7a83b2 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1638,9 +1638,12 @@ App::post('/v1/databases/transactions/:transactionId/operations') $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { $dbForProject->createDocuments('transactionLogs', $staged); - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'operations' => $existing + \count($operations), - ])); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + \count($operations) + ); }); $response @@ -1775,7 +1778,14 @@ App::patch('/v1/databases/transactions/:transactionId') Query::equal('transactionInternalId', [$transaction->getSequence()]), ]); - $creates = $updates = $deletes = $bulkUpdates = $bulkDeletes = []; + $creates + = $updates + = $deletes + = $increments + = $decrements + = $bulkUpdates + = $bulkDeletes + = []; foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; @@ -1799,15 +1809,29 @@ App::patch('/v1/databases/transactions/:transactionId') case 'delete': $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; break; + case 'increment': + $increments[$databaseInternalId][$collectionInternalId][] = [ + 'attribute' => $operation['data']['attribute'], + 'value' => $operation['data']['value'] ?? 1, + 'max' => $operation['data']['max'] ?? null, + ]; + break; + case 'decrement': + $decrements[$databaseInternalId][$collectionInternalId][] = [ + 'attribute' => $operation['data']['attribute'], + 'value' => $operation['data']['value'] ?? 1, + 'min' => $operation['data']['min'] ?? null, + ]; + break; case 'bulkUpdate': $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ - 'data' => $operation['data'] ?? null, - 'queries' => $operation['queries'] ?? [], + 'data' => $operation['data']['data'] ?? null, + 'queries' => $operation['data']['queries'] ?? [], ]; break; case 'bulkDelete': $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ - 'queries' => $operation['queries'] ?? [], + 'queries' => $operation['data']['queries'] ?? [], ]; break; } @@ -1831,6 +1855,30 @@ App::patch('/v1/databases/transactions/:transactionId') ]); } } + foreach ($increments as $dbId => $cols) { + foreach ($cols as $colId => $increments) { + foreach ($increments as $increment) { + $dbForProject->increaseDocumentAttribute( + "database_{$dbId}_collection_{$colId}", + $increment['attribute'], + $increment['value'], + $increment['max'] + ); + } + } + } + foreach ($decrements as $dbId => $cols) { + foreach ($cols as $colId => $decrements) { + foreach ($decrements as $decrement) { + $dbForProject->decreaseDocumentAttribute( + "database_{$dbId}_collection_{$colId}", + $decrement['attribute'], + $decrement['value'], + $decrement['min'] + ); + } + } + } foreach ($bulkUpdates as $dbId => $cols) { foreach ($cols as $colId => $updates) { foreach ($updates as $update) { @@ -3654,7 +3702,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('user') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, ?string $documentId, string|array|null $data, ?array $permissions, ?array $documents, ?string $transactionId, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->inject('plan') + ->action(function (string $databaseId, string $collectionId, ?string $documentId, string|array|null $data, ?array $permissions, ?array $documents, ?string $transactionId, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan) { $data = \is_string($data) ? \json_decode($data, true) : $data; @@ -3903,7 +3952,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') } try { - $dbForProject->createDocuments('transactionLogs', $operations); + $dbForProject->withTransaction(function () use ($dbForProject, $plan, $transactionId, $operations) { + $dbForProject->createDocuments('transactionLogs', $operations); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute:'operations', + value: \count($operations), + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); } catch (DuplicateException) { throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); } catch (NotFoundException) { @@ -4442,7 +4500,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) { + ->inject('plan') + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data) && \is_null($permissions)) { @@ -4592,14 +4651,22 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $document->getId(), - 'action' => 'update', - 'data' => $newDocument->getArrayCopy(), - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $plan, $transactionId, $database, $collection, $transaction, $document, $newDocument) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $document->getId(), + 'action' => 'update', + 'data' => $newDocument->getArrayCopy(), + ])); + $dbForProject->increaseDocumentAttribute( + collection:'transactions', + id: $transactionId, + attribute: 'operations', + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); } else { try { $document = $dbForProject->updateDocument( @@ -4850,13 +4917,21 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'action' => 'update', - 'data' => $newDocument->getArrayCopy(), - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $newDocument) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'update', + 'data' => $newDocument->getArrayCopy(), + ])); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute: 'operations', + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); $upserted = $newDocument; } else { @@ -4991,18 +5066,25 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $documentId, - 'action' => 'increment', - 'data' => [ - 'attribute' => $attribute, - 'value' => $value, - 'max' => $max, - ] - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $documentId, $attribute, $value, $max) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'increment', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'max' => $max, + ] + ])); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + ); + }); $document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); } else { @@ -5108,18 +5190,25 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $documentId, - 'action' => 'decrement', - 'data' => [ - 'attribute' => $attribute, - 'value' => $value, - 'min' => $min, - ], - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $documentId, $attribute, $value, $min) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'decrement', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'min' => $min, + ], + ])); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + ); + }); // Fetch current document for response without mutating $document = $dbForProject->getDocument( @@ -5249,13 +5338,21 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = []; if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'action' => 'bulkUpdate', - 'data' => \compact('data', 'queries'), - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $data, $queries) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'bulkUpdate', + 'data' => \compact('data', 'queries'), + ])); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute: 'operations', + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); } try { @@ -5368,10 +5465,19 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents') ]); } - $dbForProject->createDocuments('transactionLogs', $operations); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $operations) { + $dbForProject->createDocuments('transactionLogs', $operations); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute: 'operations', + value: \count($operations), + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); - $modified = \count($documents); - $upserted = $documents; + $modified = \count($documents); + $upserted = $documents; } else { $upserted = []; @@ -5475,13 +5581,21 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::TRANSACTION_INVALID, 'Transaction is not pending'); } - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $document->getId(), - 'action' => 'delete', - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $document) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $document->getId(), + 'action' => 'delete', + ])); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute: 'operations', + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); } else { try { $dbForProject->deleteDocument( @@ -5631,13 +5745,21 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = []; if (!empty($transactionId)) { - $dbForProject->createDocument('transactionLogs', new Document([ - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'action' => 'bulkDelete', - 'data' => ['queries' => $queries], - ])); + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $database, $collection, $transaction, $queries) { + $dbForProject->createDocument('transactionLogs', new Document([ + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'bulkDelete', + 'data' => ['queries' => $queries], + ])); + $dbForProject->increaseDocumentAttribute( + collection: 'transactions', + id: $transactionId, + attribute: 'operations', + max: $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH, + ); + }); $modified = 0; } else { From 07cce5ad3c3198d7a41d941b5fadf2c46d8c8540 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 29 Jul 2025 21:30:02 +1200 Subject: [PATCH 059/385] Optional param --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 26fe7a83b2..9fa157c49d 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -5546,7 +5546,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('documentId', '', new UID(), 'Document ID.') - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.') + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') From 480f8c97ca414113f7d2fa3d766edf4f794ee286 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 11 Aug 2025 08:25:58 +0000 Subject: [PATCH 060/385] Add transaction support for databases with staging and commit/rollback Co-authored-by: jakeb994 --- .../Documents/Attribute/Decrement.php | 3 +- .../Documents/Attribute/Increment.php | 3 +- .../Collections/Documents/Bulk/Delete.php | 3 +- .../Collections/Documents/Bulk/Update.php | 3 +- .../Collections/Documents/Bulk/Upsert.php | 3 +- .../Collections/Documents/Create.php | 3 +- .../Collections/Documents/Delete.php | 3 +- .../Collections/Documents/Update.php | 3 +- .../Collections/Documents/Upsert.php | 3 +- .../Http/Grids/Tables/Rows/Bulk/Delete.php | 1 + .../Http/Grids/Tables/Rows/Bulk/Update.php | 1 + .../Http/Grids/Tables/Rows/Bulk/Upsert.php | 1 + .../Grids/Tables/Rows/Column/Decrement.php | 1 + .../Grids/Tables/Rows/Column/Increment.php | 1 + .../Http/Grids/Tables/Rows/Create.php | 1 + .../Http/Grids/Tables/Rows/Delete.php | 1 + .../Http/Grids/Tables/Rows/Update.php | 1 + .../Http/Grids/Tables/Rows/Upsert.php | 1 + .../Http/Transactions/AddOperations.php | 117 ++++++++ .../Databases/Http/Transactions/Create.php | 73 +++++ .../Databases/Http/Transactions/Delete.php | 76 ++++++ .../Databases/Http/Transactions/Get.php | 69 +++++ .../Databases/Http/Transactions/Update.php | 249 ++++++++++++++++++ .../Databases/Http/Transactions/XList.php | 73 +++++ 24 files changed, 684 insertions(+), 9 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php 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..fa6bc93845 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 @@ -74,6 +74,7 @@ class Decrement extends Action ->param('attribute', '', new Key(), 'Attribute key.') ->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true) ->param('min', null, new Numeric(), 'Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') @@ -81,7 +82,7 @@ class Decrement extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { 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..5bfbc96c7b 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 @@ -74,6 +74,7 @@ class Increment extends Action ->param('attribute', '', new Key(), 'Attribute key.') ->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true) ->param('max', null, new Numeric(), 'Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') @@ -81,7 +82,7 @@ class Increment extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index f44e54f2b4..42206983bd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -70,6 +70,7 @@ class Delete extends Action ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') @@ -81,7 +82,7 @@ class Delete extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 82b39ef178..0a199c6eb7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -74,6 +74,7 @@ class Update extends Action ->param('collectionId', '', new UID(), 'Collection ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') @@ -85,7 +86,7 @@ class Update extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $data = \is_string($data) ? \json_decode($data, true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 23e6453138..01844b52f4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -71,6 +71,7 @@ class Upsert extends Action ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') @@ -78,7 +79,7 @@ class Upsert extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $documents, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void + public function action(string $databaseId, string $collectionId, array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { 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 0691249943..5d4629e7a5 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 @@ -117,6 +117,7 @@ class Create extends Action ->param('data', [], new JSON(), 'Document data as JSON object.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('user') @@ -127,7 +128,7 @@ class Create extends Action ->inject('queueForWebhooks') ->callback($this->action(...)); } - public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void + public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void { $data = \is_string($data) ? \json_decode($data, true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 7bc81ab7db..75377fe42b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -71,6 +71,7 @@ class Delete extends Action ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('documentId', '', new UID(), 'Document ID.') + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') @@ -79,7 +80,7 @@ class Delete extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); 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..cda4563a4c 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 @@ -77,6 +77,7 @@ class Update extends Action ->param('documentId', '', new UID(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') @@ -85,7 +86,7 @@ class Update extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index e862896a40..bdd5c1de0a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -80,6 +80,7 @@ class Upsert extends Action ->param('documentId', '', new CustomId(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('user') @@ -89,7 +90,7 @@ class Upsert extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Delete.php index 6816fc3b13..e040757b7a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Delete.php @@ -56,6 +56,7 @@ class Delete extends DocumentsDelete ->param('databaseId', '', new UID(), 'Database ID.') ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tables#tablesCreate).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Update.php index 8c640f00f8..72381ee38f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Update.php @@ -58,6 +58,7 @@ class Update extends DocumentsUpdate ->param('tableId', '', new UID(), 'Table ID.') ->param('data', [], new JSON(), 'Row data as JSON object. Include only column and value pairs to be updated.', true) ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Upsert.php index 35f0a99bdc..230f81676a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Bulk/Upsert.php @@ -58,6 +58,7 @@ class Upsert extends DocumentsUpsert ->param('databaseId', '', new UID(), 'Database ID.') ->param('tableId', '', new UID(), 'Table ID.') ->param('rows', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of row data as JSON objects. May contain partial rows.', false, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') 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..cb04e118ca 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 @@ -60,6 +60,7 @@ class Decrement extends DecrementDocumentAttribute ->param('column', '', new Key(), 'Column key.') ->param('value', 1, new Numeric(), 'Value to increment the column by. The value must be a number.', true) ->param('min', null, new Numeric(), 'Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') 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..4e36a2b912 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 @@ -60,6 +60,7 @@ class Increment extends IncrementDocumentAttribute ->param('column', '', new Key(), 'Column key.') ->param('value', 1, new Numeric(), 'Value to increment the column by. The value must be a number.', true) ->param('max', null, new Numeric(), 'Maximum value for the column. If the current value is greater than this value, an error will be thrown.', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php index d5a84bbb7c..dfbb9a61d2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php @@ -96,6 +96,7 @@ class Create extends DocumentCreate ->param('data', [], new JSON(), 'Row data as JSON object.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('rows', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') ->inject('user') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php index f821aabe6e..b13b17b8cd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php @@ -61,6 +61,7 @@ class Delete extends DocumentDelete ->param('databaseId', '', new UID(), 'Database ID.') ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tables#tablesCreate).') ->param('rowId', '', new UID(), 'Row ID.') + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php index 7731e10e9d..c6ce1a9795 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php @@ -60,6 +60,7 @@ class Update extends DocumentUpdate ->param('rowId', '', new UID(), 'Row ID.') ->param('data', [], new JSON(), 'Row data as JSON object. Include only columns and value pairs to be updated.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php index d9756eab87..157956ca18 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php @@ -62,6 +62,7 @@ class Upsert extends DocumentUpsert ->param('rowId', '', new UID(), 'Row ID.') ->param('data', [], new JSON(), 'Row data as JSON object. Include all required columns of the row to be created or updated.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('requestTimestamp') ->inject('response') ->inject('user') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php new file mode 100644 index 0000000000..197148a18a --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php @@ -0,0 +1,117 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/databases/transactions/:transactionId/operations') + ->desc('Add operations to transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'createOperations', + description: '/docs/references/databases/create-operations.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_CREATED, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('operations', [], new ArrayList(new Operation()), 'Array of staged operations.', true) + ->inject('response') + ->inject('dbForProject') + ->inject('plan') + ->callback($this->action(...)); + } + + public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, array $plan): void + { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + + if (($existing + \count($operations)) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding ' . \count($operations) . ' would exceed the maximum of ' . $maxBatch + ); + } + + $databases = $collections = $staged = []; + foreach ($operations as $operation) { + $database = $databases[$operation['databaseId']] ??= $dbForProject->getDocument('databases', $operation['databaseId']); + if ($database->isEmpty()) { + throw new Exception(Exception::DATABASE_NOT_FOUND); + } + + $collection = $collections[$operation['collectionId']] ??= $dbForProject->getDocument('database_' . $database->getSequence(), $operation['collectionId']); + if ($collection->isEmpty()) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } + + $staged[] = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $operation['documentId'] ?? ID::unique(), + 'action' => $operation['action'], + 'data' => $operation['data'] ?? new \stdClass(), + ]); + } + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { + $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + \count($operations) + ); + }); + + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php new file mode 100644 index 0000000000..9c4577c4c6 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php @@ -0,0 +1,73 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/databases/transactions') + ->desc('Create transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'createTransaction', + description: '/docs/references/databases/create-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_CREATED, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } + + public function action(int $ttl, UtopiaResponse $response, Database $dbForProject): void + { + $transaction = $dbForProject->createDocument('transactions', new Document([ + '$id' => ID::unique(), + 'status' => 'pending', + 'operations' => 0, + 'expiresAt' => DateTime::addSeconds(new \DateTime(), $ttl), + ])); + + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php new file mode 100644 index 0000000000..ae6f41fb2d --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php @@ -0,0 +1,76 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/databases/transactions/:transactionId') + ->desc('Delete transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'deleteTransaction', + description: '/docs/references/databases/delete-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_NOCONTENT, + model: UtopiaResponse::MODEL_NONE, + ) + ], + contentType: ContentType::NONE + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } + + public function action(string $transactionId, UtopiaResponse $response, Database $dbForProject): void + { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + + if (!$dbForProject->deleteDocument('transactions', $transactionId)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove transaction from DB'); + } + + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $response->noContent(); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php new file mode 100644 index 0000000000..1a8286e658 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php @@ -0,0 +1,69 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/databases/transactions/:transactionId') + ->desc('Get transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'getTransaction', + description: '/docs/references/databases/get-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } + + public function action(string $transactionId, UtopiaResponse $response, Database $dbForProject): void + { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php new file mode 100644 index 0000000000..d32e8d4c20 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -0,0 +1,249 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/databases/transactions/:transactionId') + ->desc('Update transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'updateTransaction', + description: '/docs/references/databases/update-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('commit', false, new Boolean(), 'Commit transaction?', true) + ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) + ->inject('requestTimestamp') + ->inject('response') + ->inject('dbForProject') + ->inject('project') + ->callback($this->action(...)); + } + + public function action(string $transactionId, bool $commit, bool $rollback, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Document $project): void + { + if (!$commit && !$rollback) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); + } + if ($commit && $rollback) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot commit and rollback at the same time'); + } + + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); + } + + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + + if ($commit) { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committing', + ])); + + $operations = $dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $creates + = $updates + = $deletes + = $increments + = $decrements + = $bulkUpdates + = $bulkDeletes + = []; + + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $documentId = $operation['documentId']; + + switch ($operation['action']) { + case 'create': + $creates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId ?? ID::unique(), + ...$operation['data'] + ]); + break; + case 'update': + case 'upsert': + $updates[$databaseInternalId][$collectionInternalId][] = new Document([ + '$id' => $documentId, + ...$operation['data'], + ]); + break; + case 'delete': + $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; + break; + case 'increment': + $increments[$databaseInternalId][$collectionInternalId][] = [ + 'attribute' => $operation['data']['attribute'], + 'value' => $operation['data']['value'] ?? 1, + 'max' => $operation['data']['max'] ?? null, + ]; + break; + case 'decrement': + $decrements[$databaseInternalId][$collectionInternalId][] = [ + 'attribute' => $operation['data']['attribute'], + 'value' => $operation['data']['value'] ?? 1, + 'min' => $operation['data']['min'] ?? null, + ]; + break; + case 'bulkUpdate': + $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ + 'data' => $operation['data']['data'] ?? null, + 'queries' => $operation['data']['queries'] ?? [], + ]; + break; + case 'bulkDelete': + $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ + 'queries' => $operation['data']['queries'] ?? [], + ]; + break; + } + } + + try { + foreach ($creates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createDocuments("database_{$dbId}_collection_{$colId}", $docs); + } + } + foreach ($updates as $dbId => $cols) { + foreach ($cols as $colId => $docs) { + $dbForProject->createOrUpdateDocuments("database_{$dbId}_collection_{$colId}", $docs); + } + } + foreach ($deletes as $dbId => $cols) { + foreach ($cols as $colId => $ids) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", [ + Query::equal('$id', $ids), + ]); + } + } + foreach ($increments as $dbId => $cols) { + foreach ($cols as $colId => $increments) { + foreach ($increments as $increment) { + $dbForProject->increaseDocumentAttribute( + "database_{$dbId}_collection_{$colId}", + $increment['attribute'], + $increment['value'], + $increment['max'] + ); + } + } + } + foreach ($decrements as $dbId => $cols) { + foreach ($cols as $colId => $decrements) { + foreach ($decrements as $decrement) { + $dbForProject->decreaseDocumentAttribute( + "database_{$dbId}_collection_{$colId}", + $decrement['attribute'], + $decrement['value'], + $decrement['min'] + ); + } + } + } + foreach ($bulkUpdates as $dbId => $cols) { + foreach ($cols as $colId => $updates) { + foreach ($updates as $update) { + $dbForProject->updateDocuments("database_{$dbId}_collection_{$colId}", $update['data'], $update['queries']); + } + } + } + foreach ($bulkDeletes as $dbId => $cols) { + foreach ($cols as $colId => $deletes) { + foreach ($deletes as $delete) { + $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", $delete['queries']); + } + } + } + + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committed', + ])); + + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + } catch (DuplicateException|ConflictException) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + + throw new Exception(Exception::TRANSACTION_CONFLICT); + } + }); + + $transaction = $dbForProject->getDocument('transactions', $transactionId); + } + + if ($rollback) { + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'rolledBack', + ])); + } + + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php new file mode 100644 index 0000000000..ee58807d6f --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php @@ -0,0 +1,73 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/databases/transactions') + ->desc('List transactions') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: 'transactions', + name: 'listTransactions', + description: '/docs/references/databases/list-transactions.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION_LIST, + ) + ], + contentType: ContentType::JSON + )) + ->param('queries', [], new Transactions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries).', true) + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } + + public function action(array $queries, UtopiaResponse $response, Database $dbForProject): void + { + try { + $queries = Query::parseQueries($queries); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + + $response->dynamic(new Document([ + 'transactions' => $dbForProject->find('transactions', $queries), + 'total' => $dbForProject->count('transactions', $queries), + ]), UtopiaResponse::MODEL_TRANSACTION_LIST); + } +} \ No newline at end of file From 5886c53ce3044916fb22b8ca7f74ea67e0cc6110 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 11 Aug 2025 08:43:30 +0000 Subject: [PATCH 061/385] Add transaction staging support for document operations Co-authored-by: jakeb994 --- .../Documents/Attribute/Decrement.php | 45 ++++++++++++++++ .../Documents/Attribute/Increment.php | 45 ++++++++++++++++ .../Collections/Documents/Bulk/Delete.php | 38 ++++++++++++++ .../Collections/Documents/Bulk/Update.php | 39 ++++++++++++++ .../Collections/Documents/Bulk/Upsert.php | 39 ++++++++++++++ .../Collections/Documents/Create.php | 51 +++++++++++++++++++ .../Collections/Documents/Delete.php | 33 ++++++++++++ .../Collections/Documents/Update.php | 42 +++++++++++++++ .../Collections/Documents/Upsert.php | 41 +++++++++++++++ 9 files changed, 373 insertions(+) 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 fa6bc93845..8742499348 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 @@ -94,6 +94,51 @@ class Decrement extends Action throw new Exception($this->getParentNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'decrement', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'min' => $min, + ], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually decrementing + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + $attribute => $value, // Mock response - actual value would be computed during commit + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + try { $document = $dbForProject->decreaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), 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 5bfbc96c7b..f497d781f1 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 @@ -94,6 +94,51 @@ class Increment extends Action throw new Exception($this->getParentNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'increment', + 'data' => [ + 'attribute' => $attribute, + 'value' => $value, + 'max' => $max, + ], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually incrementing + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + $attribute => $value, // Mock response - actual value would be computed during commit + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + try { $document = $dbForProject->increaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 42206983bd..19c9c7f005 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -109,6 +109,44 @@ class Delete extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => null, // Bulk operation doesn't have specific document ID + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => $queries, + ], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually deleting documents + $response->dynamic(new Document([ + $this->getSdkGroup() => [], + 'total' => 0, // Can't predict how many would be deleted + ]), $this->getResponseModel()); + return; + } + $documents = []; try { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 0a199c6eb7..c2e2218c4e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -121,6 +121,45 @@ class Update extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => null, // Bulk operation doesn't have specific document ID + 'action' => 'bulkUpdate', + 'data' => [ + 'data' => $data, + 'queries' => $queries, + ], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually updating documents + $response->dynamic(new Document([ + $this->getSdkGroup() => [], + 'total' => 0, // Can't predict how many would be updated + ]), $this->getResponseModel()); + return; + } + if ($data['$permissions']) { $validator = new Permissions(); if (!$validator->isValid($data['$permissions'])) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 01844b52f4..ba05ce414d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -100,6 +100,45 @@ class Upsert extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operations in transaction logs + $staged = []; + foreach ($documents as $document) { + $staged[] = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $document['$id'] ?? ID::unique(), + 'action' => 'upsert', + 'data' => $document, + ]); + } + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + \count($staged) + ); + }); + + // Return successful response without actually upserting documents + $response->dynamic(new Document([ + $this->getSdkGroup() => [], + 'total' => \count($documents), + ]), $this->getResponseModel()); + return; + } + foreach ($documents as $key => $document) { $documents[$key] = new Document($document); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 5d4629e7a5..bf447454ed 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 @@ -189,6 +189,57 @@ class Create extends Action throw new Exception($this->getParentNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation(s) in transaction logs + $staged = []; + foreach ($documents as $document) { + $staged[] = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $document['$id'] ?? $documentId ?? ID::unique(), + 'action' => 'create', + 'data' => $document, + ]); + } + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + \count($staged) + ); + }); + + // Return successful response without actually creating documents + if ($isBulk) { + $response->dynamic(new Document([ + $this->getSdkGroup() => [], + 'total' => \count($documents), + ]), $this->getBulkResponseModel()); + } else { + $mockDocument = new Document([ + '$id' => $documents[0]['$id'] ?? $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$documents[0] + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($mockDocument, $this->getResponseModel()); + } + return; + } + $hasRelationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 75377fe42b..e6dc7e1555 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -104,6 +104,39 @@ class Delete extends Action throw new Exception($this->getNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'delete', + 'data' => [], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually deleting document + $response->noContent(); + return; + } + try { $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { $dbForProject->deleteDocument( 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 cda4563a4c..33a1ebcc7a 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 @@ -128,6 +128,48 @@ class Update extends Action throw new Exception($this->getNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'update', + 'data' => $data, + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually updating document + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$document->getArrayCopy(), + ...$data + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions, [ Database::PERMISSION_READ, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index bdd5c1de0a..61fdbd312d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -111,6 +111,47 @@ class Upsert extends Action throw new Exception($this->getParentNotFoundException()); } + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'upsert', + 'data' => $data, + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually upserting document + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$data + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + $allowedPermissions = [ Database::PERMISSION_READ, Database::PERMISSION_UPDATE, From 5cec34b802ce310b7d24f113ebd7722eca5d09d4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 12 Aug 2025 00:45:30 +1200 Subject: [PATCH 062/385] Fix merge --- src/Appwrite/Utopia/Response.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 8cf5899823..1e8f9d5ee8 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -218,6 +218,10 @@ class Response extends SwooleResponse public const MODEL_COLUMN_DATETIME = 'columnDatetime'; public const MODEL_COLUMN_RELATIONSHIP = 'columnRelationship'; + // Transactions + public const MODEL_TRANSACTION = 'transaction'; + public const MODEL_TRANSACTION_LIST = 'transactionList'; + // Users public const MODEL_ACCOUNT = 'account'; public const MODEL_USER = 'user'; From a71333ac60ff164c482dc13fe0f7dfb2782e594f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 12 Aug 2025 12:00:28 +0000 Subject: [PATCH 063/385] Refactor transaction replay to preserve exact order and timestamps Co-authored-by: jakeb994 --- .../Databases/Http/Transactions/Update.php | 184 +++++++----------- 1 file changed, 72 insertions(+), 112 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index d32e8d4c20..8793847a38 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -93,124 +93,84 @@ class Update extends Action 'status' => 'committing', ])); + // Fetch operations ordered by creation time to maintain exact sequence $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc('$createdAt'), ]); - $creates - = $updates - = $deletes - = $increments - = $decrements - = $bulkUpdates - = $bulkDeletes - = []; - - foreach ($operations as $operation) { - $databaseInternalId = $operation['databaseInternalId']; - $collectionInternalId = $operation['collectionInternalId']; - $documentId = $operation['documentId']; - - switch ($operation['action']) { - case 'create': - $creates[$databaseInternalId][$collectionInternalId][] = new Document([ - '$id' => $documentId ?? ID::unique(), - ...$operation['data'] - ]); - break; - case 'update': - case 'upsert': - $updates[$databaseInternalId][$collectionInternalId][] = new Document([ - '$id' => $documentId, - ...$operation['data'], - ]); - break; - case 'delete': - $deletes[$databaseInternalId][$collectionInternalId][] = $documentId; - break; - case 'increment': - $increments[$databaseInternalId][$collectionInternalId][] = [ - 'attribute' => $operation['data']['attribute'], - 'value' => $operation['data']['value'] ?? 1, - 'max' => $operation['data']['max'] ?? null, - ]; - break; - case 'decrement': - $decrements[$databaseInternalId][$collectionInternalId][] = [ - 'attribute' => $operation['data']['attribute'], - 'value' => $operation['data']['value'] ?? 1, - 'min' => $operation['data']['min'] ?? null, - ]; - break; - case 'bulkUpdate': - $bulkUpdates[$databaseInternalId][$collectionInternalId][] = [ - 'data' => $operation['data']['data'] ?? null, - 'queries' => $operation['data']['queries'] ?? [], - ]; - break; - case 'bulkDelete': - $bulkDeletes[$databaseInternalId][$collectionInternalId][] = [ - 'queries' => $operation['data']['queries'] ?? [], - ]; - break; - } - } - try { - foreach ($creates as $dbId => $cols) { - foreach ($cols as $colId => $docs) { - $dbForProject->createDocuments("database_{$dbId}_collection_{$colId}", $docs); - } - } - foreach ($updates as $dbId => $cols) { - foreach ($cols as $colId => $docs) { - $dbForProject->createOrUpdateDocuments("database_{$dbId}_collection_{$colId}", $docs); - } - } - foreach ($deletes as $dbId => $cols) { - foreach ($cols as $colId => $ids) { - $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", [ - Query::equal('$id', $ids), - ]); - } - } - foreach ($increments as $dbId => $cols) { - foreach ($cols as $colId => $increments) { - foreach ($increments as $increment) { - $dbForProject->increaseDocumentAttribute( - "database_{$dbId}_collection_{$colId}", - $increment['attribute'], - $increment['value'], - $increment['max'] - ); + // Replay operations in exact order they were created + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $documentId = $operation['documentId']; + $action = $operation['action']; + $data = $operation['data']; + $operationCreatedAt = new \DateTime($operation['$createdAt']); + + $collectionName = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; + + // Wrap each operation with the timestamp from when it was logged + $dbForProject->withRequestTimestamp($operationCreatedAt, function () use ($dbForProject, $action, $collectionName, $documentId, $data) { + switch ($action) { + case 'create': + $document = new Document([ + '$id' => $documentId ?? ID::unique(), + ...$data + ]); + $dbForProject->createDocument($collectionName, $document); + break; + + case 'update': + case 'upsert': + $document = new Document([ + '$id' => $documentId, + ...$data, + ]); + $dbForProject->createOrUpdateDocument($collectionName, $document); + break; + + case 'delete': + $dbForProject->deleteDocument($collectionName, $documentId); + break; + + case 'increment': + $dbForProject->increaseDocumentAttribute( + collection: $collectionName, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + max: $data['max'] ?? null + ); + break; + + case 'decrement': + $dbForProject->decreaseDocumentAttribute( + collection: $collectionName, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + min: $data['min'] ?? null + ); + break; + + case 'bulkUpdate': + $dbForProject->updateDocuments( + $collectionName, + $data['data'] ?? null, + $data['queries'] ?? [] + ); + break; + + case 'bulkDelete': + $dbForProject->deleteDocuments( + $collectionName, + $data['queries'] ?? [] + ); + break; } - } - } - foreach ($decrements as $dbId => $cols) { - foreach ($cols as $colId => $decrements) { - foreach ($decrements as $decrement) { - $dbForProject->decreaseDocumentAttribute( - "database_{$dbId}_collection_{$colId}", - $decrement['attribute'], - $decrement['value'], - $decrement['min'] - ); - } - } - } - foreach ($bulkUpdates as $dbId => $cols) { - foreach ($cols as $colId => $updates) { - foreach ($updates as $update) { - $dbForProject->updateDocuments("database_{$dbId}_collection_{$colId}", $update['data'], $update['queries']); - } - } - } - foreach ($bulkDeletes as $dbId => $cols) { - foreach ($cols as $colId => $deletes) { - foreach ($deletes as $delete) { - $dbForProject->deleteDocuments("database_{$dbId}_collection_{$colId}", $delete['queries']); - } - } + }); } $dbForProject->updateDocument('transactions', $transactionId, new Document([ From 6d477688d74a53b163b090066fd6e7ba03739bf9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 09:50:45 +0000 Subject: [PATCH 064/385] Enforce max operations per transaction in database operations Co-authored-by: jakeb994 --- .../Collections/Documents/Attribute/Decrement.php | 15 ++++++++++++++- .../Collections/Documents/Attribute/Increment.php | 15 ++++++++++++++- .../Collections/Documents/Bulk/Delete.php | 10 ++++++++++ .../Collections/Documents/Bulk/Update.php | 10 ++++++++++ .../Collections/Documents/Bulk/Upsert.php | 10 ++++++++++ .../Databases/Collections/Documents/Create.php | 13 ++++++++++++- .../Databases/Collections/Documents/Delete.php | 15 ++++++++++++++- .../Databases/Collections/Documents/Update.php | 15 ++++++++++++++- .../Databases/Collections/Documents/Upsert.php | 13 ++++++++++++- 9 files changed, 110 insertions(+), 6 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 8742499348..b383bffbe6 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 @@ -79,10 +79,13 @@ class Decrement extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { @@ -101,6 +104,16 @@ class Decrement extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), 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 f497d781f1..311c07dedf 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 @@ -79,10 +79,13 @@ class Increment extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { @@ -101,6 +104,16 @@ class Increment extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 19c9c7f005..b52f38cf7b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -116,6 +116,16 @@ class Delete extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index c2e2218c4e..c6ea163604 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -128,6 +128,16 @@ class Update extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index ba05ce414d..50ca33d002 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -107,6 +107,16 @@ class Upsert extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + \count($documents)) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operations in transaction logs $staged = []; foreach ($documents as $document) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index bf447454ed..3a544bb869 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 @@ -126,9 +126,10 @@ class Create extends Action ->inject('queueForRealtime') ->inject('queueForFunctions') ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void + public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $data = \is_string($data) ? \json_decode($data, true) @@ -196,6 +197,16 @@ class Create extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + \count($documents)) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation(s) in transaction logs $staged = []; foreach ($documents as $document) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index e6dc7e1555..c6a2218f54 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -77,10 +77,13 @@ class Delete extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -111,6 +114,16 @@ class Delete extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), 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 33a1ebcc7a..a616f592cb 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 @@ -83,10 +83,13 @@ class Update extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -135,6 +138,16 @@ class Update extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 61fdbd312d..b69f58f48b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -87,10 +87,11 @@ class Upsert extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -118,6 +119,16 @@ class Upsert extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + // Enforce max operations per transaction + $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + // Stage the operation in transaction logs $staged = new Document([ '$id' => ID::unique(), From dffe030d16ee4ae53297329eb339abf384d86656 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 10:06:43 +0000 Subject: [PATCH 065/385] Replace databasesBatchSize with databasesTransactionSize in database operations Co-authored-by: jakeb994 --- app/init/constants.php | 1 + .../Databases/Collections/Documents/Attribute/Decrement.php | 2 +- .../Databases/Collections/Documents/Attribute/Increment.php | 2 +- .../Http/Databases/Collections/Documents/Bulk/Delete.php | 2 +- .../Http/Databases/Collections/Documents/Bulk/Update.php | 2 +- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 2 +- .../Databases/Http/Databases/Collections/Documents/Create.php | 2 +- .../Databases/Http/Databases/Collections/Documents/Delete.php | 2 +- .../Databases/Http/Databases/Collections/Documents/Update.php | 2 +- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 2 +- .../Modules/Databases/Http/Transactions/AddOperations.php | 2 +- 11 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index d217174215..c5b83f2594 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -31,6 +31,7 @@ const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls const APP_LIMIT_DATABASE_BATCH = 100; // Default maximum batch size for database operations +const APP_LIMIT_DATABASE_TRANSACTION = 100; // Default maximum operations per transaction const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours 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 b383bffbe6..7013e4bce6 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 @@ -105,7 +105,7 @@ class Decrement extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( 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 311c07dedf..24a521724b 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 @@ -105,7 +105,7 @@ class Increment extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index b52f38cf7b..0c27ea420a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -117,7 +117,7 @@ class Delete extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index c6ea163604..8cca45661d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -129,7 +129,7 @@ class Update extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 50ca33d002..0868a2519b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -108,7 +108,7 @@ class Upsert extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + \count($documents)) > $maxBatch) { throw new Exception( 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 3a544bb869..e0d7d03180 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 @@ -198,7 +198,7 @@ class Create extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + \count($documents)) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index c6a2218f54..59f72d727d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -115,7 +115,7 @@ class Delete extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( 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 a616f592cb..76d3bc692b 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 @@ -139,7 +139,7 @@ class Update extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index b69f58f48b..fff6c53978 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -120,7 +120,7 @@ class Upsert extends Action } // Enforce max operations per transaction - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + 1) > $maxBatch) { throw new Exception( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php index 197148a18a..d8d31f0d8f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php @@ -67,7 +67,7 @@ class AddOperations extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } - $maxBatch = $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH; + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); if (($existing + \count($operations)) > $maxBatch) { From 335052bc847930012482f6e8ebfb7ad3ffebb74d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 10:17:54 +0000 Subject: [PATCH 066/385] Remove unused function and webhook event queue injections in database actions Co-authored-by: jakeb994 --- .../Databases/Collections/Documents/Attribute/Decrement.php | 4 +--- .../Databases/Collections/Documents/Attribute/Increment.php | 4 +--- .../Databases/Http/Databases/Collections/Documents/Create.php | 4 +--- .../Databases/Http/Databases/Collections/Documents/Delete.php | 4 +--- .../Databases/Http/Databases/Collections/Documents/Update.php | 4 +--- 5 files changed, 5 insertions(+), 15 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 7013e4bce6..f71ed4f187 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 @@ -79,13 +79,11 @@ class Decrement extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { 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 24a521724b..d9d9beefa7 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 @@ -79,13 +79,11 @@ class Increment extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { 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 e0d7d03180..fe8b688a54 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 @@ -124,12 +124,10 @@ class Create extends Action ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, array $plan): void { $data = \is_string($data) ? \json_decode($data, true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 59f72d727d..b176d24ef5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -77,13 +77,11 @@ class Delete extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); 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 76d3bc692b..611652b48d 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 @@ -83,13 +83,11 @@ class Update extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array From 31a7df721488e704be720092fafc04faf8dab215 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 22:20:30 +1200 Subject: [PATCH 067/385] Import fixes --- .../Http/Databases/Collections/Documents/Bulk/Delete.php | 1 + .../Http/Databases/Collections/Documents/Bulk/Update.php | 3 ++- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 1 + .../Http/Databases/Collections/Documents/Create.php | 7 +++++-- .../Http/Databases/Collections/Documents/Delete.php | 9 +++++++-- .../Http/Databases/Collections/Documents/Update.php | 8 ++++++-- .../Http/Databases/Collections/Documents/Upsert.php | 7 +++++-- .../Modules/Databases/Http/Transactions/Create.php | 3 +-- .../{AddOperations.php => Operations/Create.php} | 4 ++-- 9 files changed, 30 insertions(+), 13 deletions(-) rename src/Appwrite/Platform/Modules/Databases/Http/Transactions/{AddOperations.php => Operations/Create.php} (97%) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 19c9c7f005..727daa8576 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -17,6 +17,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Restricted as RestrictedException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index c2e2218c4e..d34c819fc4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -18,6 +18,7 @@ use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Relationship as RelationshipException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\UID; @@ -108,7 +109,7 @@ class Update extends Action $hasRelationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); if ($hasRelationships) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index ba05ce414d..ca94c19302 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -17,6 +17,7 @@ use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Relationship as RelationshipException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\ArrayList; 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 1090b869f5..2eb1896b51 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 @@ -194,8 +194,11 @@ class Create extends Action // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); } // Stage the operation(s) in transaction logs diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index e6dc7e1555..925b046d17 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -13,8 +13,10 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Restricted as RestrictedException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; @@ -107,8 +109,11 @@ class Delete extends Action // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); } // Stage the operation in transaction logs 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 33a1ebcc7a..ef65fe9d40 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 @@ -120,6 +120,7 @@ class Update extends Action throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); } } + // Read permission should not be required for update /** @var Document $document */ $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); @@ -131,8 +132,11 @@ class Update extends Action // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); } // Stage the operation in transaction logs diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 61fdbd312d..e598a69be7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -114,8 +114,11 @@ class Upsert extends Action // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); } // Stage the operation in transaction logs diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php index 9c4577c4c6..25a16e19e3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php @@ -9,10 +9,9 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; -use Utopia\Database\Validator\UID; -use Utopia\DateTime\DateTime; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\Range; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php similarity index 97% rename from src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php rename to src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php index 197148a18a..40f03e4ccb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/AddOperations.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php @@ -1,6 +1,6 @@ Date: Thu, 14 Aug 2025 22:22:12 +1200 Subject: [PATCH 068/385] Fix create inject --- .../Databases/Http/Databases/Collections/Documents/Create.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 0327677372..8fd334fd1e 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 @@ -126,10 +126,12 @@ class Create extends Action ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForRealtime') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, array $plan): void + public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { $data = \is_string($data) ? \json_decode($data, true) From 76034f801b5893a6a756d0ccbaaee7edce3d47a0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:31:27 +1200 Subject: [PATCH 069/385] Add failed error --- app/config/errors.php | 7 ++++++- src/Appwrite/Extend/Exception.php | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/config/errors.php b/app/config/errors.php index fe64a0ce05..d786940c07 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -963,7 +963,12 @@ return [ ], Exception::TRANSACTION_INVALID => [ 'name' => Exception::TRANSACTION_INVALID, - 'description' => 'The transaction is invalid. Please check the transaction data and try again.', + 'description' => 'The transaction is invalid. Please check the transaction state and try again.', + 'code' => 400, + ], + Exception::TRANSACTION_FAILED => [ + 'name' => Exception::TRANSACTION_FAILED, + 'description' => 'The transaction has errored. Please check the transaction data and try again.', 'code' => 400, ], Exception::TRANSACTION_EXPIRED => [ diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 73185d8fab..2465623d18 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -267,6 +267,7 @@ class Exception extends \Exception public const TRANSACTION_NOT_FOUND = 'transaction_not_found'; public const TRANSACTION_ALREADY_EXISTS = 'transaction_already_exists'; public const TRANSACTION_INVALID = 'transaction_invalid'; + public const TRANSACTION_FAILED = 'transaction_expired'; public const TRANSACTION_EXPIRED = 'transaction_expired'; public const TRANSACTION_CONFLICT = 'transaction_conflict'; public const TRANSACTION_LIMIT_EXCEEDED = 'transaction_limit_exceeded'; From 22358482627f7ade32392ba567283386ec61b2c8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:32:03 +1200 Subject: [PATCH 070/385] Clear txn logs on worker --- app/init/constants.php | 2 ++ .../Databases/Http/Transactions/Update.php | 13 +++++++------ src/Appwrite/Platform/Workers/Deletes.php | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index c5b83f2594..e811fbe306 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -105,8 +105,10 @@ const BUILD_TYPE_RETRY = 'retry'; // Deletion Types const DELETE_TYPE_DATABASES = 'databases'; + const DELETE_TYPE_DOCUMENT = 'document'; const DELETE_TYPE_COLLECTIONS = 'collections'; +const DELETE_TYPE_TRANSACTION = 'transaction'; const DELETE_TYPE_PROJECTS = 'projects'; const DELETE_TYPE_SITES = 'sites'; const DELETE_TYPE_FUNCTIONS = 'functions'; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 8793847a38..db17157783 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -177,9 +177,10 @@ class Update extends Action 'status' => 'committed', ])); - $dbForProject->deleteDocuments('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - ]); + // Clear the transaction logs + $queueForDeletes + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($transaction); } catch (DuplicateException|ConflictException) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', @@ -193,9 +194,9 @@ class Update extends Action } if ($rollback) { - $dbForProject->deleteDocuments('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - ]); + $queueForDeletes + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($transaction); $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'rolledBack', diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index aa511c2209..c7dda8ccf0 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -130,6 +130,9 @@ class Deletes extends Action case DELETE_TYPE_RULES: $this->deleteRule($dbForPlatform, $document, $certificates); break; + case DELETE_TYPE_TRANSACTION: + $this->deleteTransactionLogs($getProjectDB, $document, $project); + break; default: Console::error('No lazy delete operation available for document of type: ' . $document->getCollection()); break; @@ -1299,4 +1302,20 @@ class Deletes extends Action ); } } + + private function deleteTransactionLogs(callable $getProjectDB, Document $document, Document $project): void + { + $dbForProject = $getProjectDB($project); + $transactionId = $document->getId(); + $transactionInternalId = $document->getSequence(); + + try { + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', [$transactionInternalId]), + ]); + Console::info("Transaction logs for {$transactionId} deleted."); + } catch (Throwable $th) { + Console::error("Failed to delete transaction logs for {$transactionId}: " . $th->getMessage()); + } + } } From 3d1ae4feda6d0ab38a050cc2c179305de9bc717b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:34:20 +1200 Subject: [PATCH 071/385] Process permissions before txn logging --- .../Documents/Attribute/Decrement.php | 8 +- .../Documents/Attribute/Increment.php | 4 +- .../Collections/Documents/Bulk/Delete.php | 2 - .../Collections/Documents/Bulk/Update.php | 15 ++- .../Collections/Documents/Bulk/Upsert.php | 33 +++--- .../Collections/Documents/Update.php | 112 +++++++++--------- .../Collections/Documents/Upsert.php | 108 ++++++++--------- 7 files changed, 141 insertions(+), 141 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 f71ed4f187..1a6a7477af 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 @@ -13,10 +13,12 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Type as TypeException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\UID; @@ -85,12 +87,12 @@ class Decrement extends Action public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); + $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); if ($collection->isEmpty()) { throw new Exception($this->getParentNotFoundException()); } @@ -142,7 +144,7 @@ class Decrement extends Action '$id' => $documentId, '$collectionId' => $collectionId, '$databaseId' => $databaseId, - $attribute => $value, // Mock response - actual value would be computed during commit + $attribute => $value, ]); $response ->setStatusCode(SwooleResponse::STATUS_CODE_OK) 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 d9d9beefa7..b4246377ca 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 @@ -13,10 +13,12 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Type as TypeException; +use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\UID; @@ -142,7 +144,7 @@ class Increment extends Action '$id' => $documentId, '$collectionId' => $collectionId, '$databaseId' => $databaseId, - $attribute => $value, // Mock response - actual value would be computed during commit + $attribute => $value, ]); $response ->setStatusCode(SwooleResponse::STATUS_CODE_OK) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index da919a64bf..fedbf7d5b5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -133,7 +133,6 @@ class Delete extends Action 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => null, // Bulk operation doesn't have specific document ID 'action' => 'bulkDelete', 'data' => [ 'queries' => $queries, @@ -146,7 +145,6 @@ class Delete extends Action 'transactions', $transactionId, 'operations', - 1 ); }); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index fdc8efdd83..caaeaced1a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -122,6 +122,13 @@ class Update extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } + if ($data['$permissions']) { + $validator = new Permissions(); + if (!$validator->isValid($data['$permissions'])) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription()); + } + } + // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); @@ -145,7 +152,6 @@ class Update extends Action 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => null, // Bulk operation doesn't have specific document ID 'action' => 'bulkUpdate', 'data' => [ 'data' => $data, @@ -171,13 +177,6 @@ class Update extends Action return; } - if ($data['$permissions']) { - $validator = new Permissions(); - if (!$validator->isValid($data['$permissions'])) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription()); - } - } - $documents = []; try { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index e3cbe56752..b3288cb4fb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -101,6 +101,10 @@ class Upsert extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); } + foreach ($documents as $key => $document) { + $documents[$key] = new Document($document); + } + // Handle transaction staging if ($transactionId !== null) { $transaction = $dbForProject->getDocument('transactions', $transactionId); @@ -111,7 +115,7 @@ class Upsert extends Action // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); - if (($existing + \count($documents)) > $maxBatch) { + if (($existing + 1) > $maxBatch) { throw new Exception( Exception::TRANSACTION_LIMIT_EXCEEDED, 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch @@ -119,21 +123,17 @@ class Upsert extends Action } // Stage the operations in transaction logs - $staged = []; - foreach ($documents as $document) { - $staged[] = new Document([ - '$id' => ID::unique(), - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $document['$id'] ?? ID::unique(), - 'action' => 'upsert', - 'data' => $document, - ]); - } + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'action' => 'bulkUpsert', + 'data' => $documents, + ]); $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { - $dbForProject->createDocuments('transactionLogs', $staged); + $dbForProject->createDocument('transactionLogs', $staged); $dbForProject->increaseDocumentAttribute( 'transactions', $transactionId, @@ -147,11 +147,8 @@ class Upsert extends Action $this->getSdkGroup() => [], 'total' => \count($documents), ]), $this->getResponseModel()); - return; - } - foreach ($documents as $key => $document) { - $documents[$key] = new Document($document); + return; } $upserted = []; 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 5ae817dbed..0332ca3673 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 @@ -130,61 +130,6 @@ class Update extends Action throw new Exception($this->getNotFoundException()); } - // Handle transaction staging - if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty()) { - throw new Exception(Exception::TRANSACTION_NOT_FOUND); - } - if ($transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::TRANSACTION_NOT_READY); - } - - // Enforce max operations per transaction - $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; - $existing = $transaction->getAttribute('operations', 0); - if (($existing + 1) > $maxBatch) { - throw new Exception( - Exception::TRANSACTION_LIMIT_EXCEEDED, - 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch - ); - } - - // Stage the operation in transaction logs - $staged = new Document([ - '$id' => ID::unique(), - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $documentId, - 'action' => 'update', - 'data' => $data, - ]); - - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { - $dbForProject->createDocument('transactionLogs', $staged); - $dbForProject->increaseDocumentAttribute( - 'transactions', - $transactionId, - 'operations', - 1 - ); - }); - - // Return successful response without actually updating document - $mockDocument = new Document([ - '$id' => $documentId, - '$collectionId' => $collectionId, - '$databaseId' => $databaseId, - ...$document->getArrayCopy(), - ...$data - ]); - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_OK) - ->dynamic($mockDocument, $this->getResponseModel()); - return; - } - // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions, [ Database::PERMISSION_READ, @@ -298,6 +243,63 @@ class Update extends Action ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations); + + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); + } + + // Enforce max operations per transaction + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'update', + 'data' => $data, + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually updating document + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$document->getArrayCopy(), + ...$data + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_OK) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + + try { $document = $dbForProject->withRequestTimestamp( $requestTimestamp, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index e1a4153518..316fe4f484 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -112,60 +112,6 @@ class Upsert extends Action throw new Exception($this->getParentNotFoundException()); } - // Handle transaction staging - if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty()) { - throw new Exception(Exception::TRANSACTION_NOT_FOUND); - } - if ($transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::TRANSACTION_NOT_READY); - } - - // Enforce max operations per transaction - $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; - $existing = $transaction->getAttribute('operations', 0); - if (($existing + 1) > $maxBatch) { - throw new Exception( - Exception::TRANSACTION_LIMIT_EXCEEDED, - 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch - ); - } - - // Stage the operation in transaction logs - $staged = new Document([ - '$id' => ID::unique(), - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $documentId, - 'action' => 'upsert', - 'data' => $data, - ]); - - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { - $dbForProject->createDocument('transactionLogs', $staged); - $dbForProject->increaseDocumentAttribute( - 'transactions', - $transactionId, - 'operations', - 1 - ); - }); - - // Return successful response without actually upserting document - $mockDocument = new Document([ - '$id' => $documentId, - '$collectionId' => $collectionId, - '$databaseId' => $databaseId, - ...$data - ]); - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) - ->dynamic($mockDocument, $this->getResponseModel()); - return; - } - $allowedPermissions = [ Database::PERMISSION_READ, Database::PERMISSION_UPDATE, @@ -300,6 +246,60 @@ class Upsert extends Action ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); + } + + // Enforce max operations per transaction + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch + ); + } + + // Stage the operation in transaction logs + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $documentId, + 'action' => 'upsert', + 'data' => $data, + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + 1 + ); + }); + + // Return successful response without actually upserting document + $mockDocument = new Document([ + '$id' => $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$data + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($mockDocument, $this->getResponseModel()); + return; + } + $upserted = []; try { $dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) { From ede88a533ec31cf6ef374ee54f79e6af03ee6ae1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:35:06 +1200 Subject: [PATCH 072/385] Handle create single/bulk split --- .../Collections/Documents/Create.php | 123 +++++++++--------- 1 file changed, 59 insertions(+), 64 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 8fd334fd1e..5e7c20b76d 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 @@ -192,70 +192,6 @@ class Create extends Action throw new Exception($this->getParentNotFoundException()); } - // Handle transaction staging - if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty()) { - throw new Exception(Exception::TRANSACTION_NOT_FOUND); - } - if ($transaction->getAttribute('status', '') !== 'pending') { - throw new Exception(Exception::TRANSACTION_NOT_READY); - } - - // Enforce max operations per transaction - $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; - $existing = $transaction->getAttribute('operations', 0); - if (($existing + \count($documents)) > $maxBatch) { - throw new Exception( - Exception::TRANSACTION_LIMIT_EXCEEDED, - 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch - ); - } - - // Stage the operation(s) in transaction logs - $staged = []; - foreach ($documents as $document) { - $staged[] = new Document([ - '$id' => ID::unique(), - 'databaseInternalId' => $database->getSequence(), - 'collectionInternalId' => $collection->getSequence(), - 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $document['$id'] ?? $documentId ?? ID::unique(), - 'action' => 'create', - 'data' => $document, - ]); - } - - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { - $dbForProject->createDocuments('transactionLogs', $staged); - $dbForProject->increaseDocumentAttribute( - 'transactions', - $transactionId, - 'operations', - \count($staged) - ); - }); - - // Return successful response without actually creating documents - if ($isBulk) { - $response->dynamic(new Document([ - $this->getSdkGroup() => [], - 'total' => \count($documents), - ]), $this->getBulkResponseModel()); - } else { - $mockDocument = new Document([ - '$id' => $documents[0]['$id'] ?? $documentId, - '$collectionId' => $collectionId, - '$databaseId' => $databaseId, - ...$documents[0] - ]); - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) - ->dynamic($mockDocument, $this->getResponseModel()); - } - return; - } - $hasRelationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP @@ -439,6 +375,65 @@ class Create extends Action return $document; }, $documents); + // Handle transaction staging + if ($transactionId !== null) { + $transaction = $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { + throw new Exception(Exception::TRANSACTION_NOT_READY); + } + + // Enforce max operations per transaction + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; + $existing = $transaction->getAttribute('operations', 0); + if (($existing + 1) > $maxBatch) { + throw new Exception( + Exception::TRANSACTION_LIMIT_EXCEEDED, + 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch + ); + } + + $staged = new Document([ + '$id' => ID::unique(), + 'databaseInternalId' => $database->getSequence(), + 'collectionInternalId' => $collection->getSequence(), + 'transactionInternalId' => $transaction->getSequence(), + 'documentId' => $isBulk ? null: $documentId, + 'action' => $isBulk ? 'bulkCreate' : 'create', + 'data' => $isBulk ? $documents : $documents[0], + ]); + + $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) { + $dbForProject->createDocument('transactionLogs', $staged); + $dbForProject->increaseDocumentAttribute( + 'transactions', + $transactionId, + 'operations', + ); + }); + + // Return successful response without actually creating documents + if ($isBulk) { + $response->dynamic(new Document([ + $this->getSdkGroup() => [], + 'total' => \count($documents), + ]), $this->getBulkResponseModel()); + } else { + $mockDocument = new Document([ + '$id' => $documents[0]['$id'] ?? $documentId, + '$collectionId' => $collectionId, + '$databaseId' => $databaseId, + ...$documents[0] + ]); + $response + ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) + ->dynamic($mockDocument, $this->getResponseModel()); + } + return; + } + try { $dbForProject->withPreserveDates( fn () => $dbForProject->createDocuments( From 84bf2641aae9d813f5fdbe425a2c4ff31858da63 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:35:20 +1200 Subject: [PATCH 073/385] Fix scope --- .../Platform/Modules/Databases/Http/Transactions/Update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index db17157783..d41cdbf20d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -38,7 +38,7 @@ class Update extends Action ->setHttpPath('/v1/databases/transactions/:transactionId') ->desc('Update transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'collections.write') + ->label('scope', 'transactions.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', From 3a7d8d2296fdfdf0ef065fc29be436820fc99138 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:35:41 +1200 Subject: [PATCH 074/385] Remove redundant order --- .../Modules/Databases/Http/Transactions/Update.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index d41cdbf20d..d7da3c0df5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -57,14 +57,13 @@ class Update extends Action ->param('transactionId', '', new UID(), 'Transaction ID.') ->param('commit', false, new Boolean(), 'Commit transaction?', true) ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) - ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('project') + ->inject('queueForDeletes') ->callback($this->action(...)); } - public function action(string $transactionId, bool $commit, bool $rollback, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Document $project): void + public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Delete $queueForDeletes): void { if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); @@ -88,15 +87,14 @@ class Update extends Action } if ($commit) { - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $transaction, $requestTimestamp) { + $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, $transaction) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', ])); - // Fetch operations ordered by creation time to maintain exact sequence + // Fetch operations ordered by sequence by default $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc('$createdAt'), ]); try { From d6544f412de4742e341afdbbe4e5cb0d0be63a02 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:36:26 +1200 Subject: [PATCH 075/385] Add missing bulk cases on commit --- .../Databases/Http/Transactions/Update.php | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index d7da3c0df5..0240f3e9f6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -152,14 +152,30 @@ class Update extends Action min: $data['min'] ?? null ); break; - + + case 'bulkCreate': + $documents = []; + foreach ($data as $docData) { + $documents[] = new Document($docData); + } + $dbForProject->createDocuments($collectionId, $documents); + break; + case 'bulkUpdate': $dbForProject->updateDocuments( - $collectionName, + $collectionId, $data['data'] ?? null, $data['queries'] ?? [] ); break; + + case 'bulkUpsert': + $documents = []; + foreach ($data as $docData) { + $documents[] = new Document($docData); + } + $dbForProject->createOrUpdateDocuments($collectionId, $documents); + break; case 'bulkDelete': $dbForProject->deleteDocuments( From fb31fbea9bf06a593b60a3e211c9e24187f1cc53 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:36:42 +1200 Subject: [PATCH 076/385] Ensure parsed queries --- .../Platform/Modules/Databases/Http/Transactions/Update.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 0240f3e9f6..419d83eb41 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -165,7 +165,7 @@ class Update extends Action $dbForProject->updateDocuments( $collectionId, $data['data'] ?? null, - $data['queries'] ?? [] + Query::parseQueries($data['queries'] ?? []) ); break; @@ -179,8 +179,8 @@ class Update extends Action case 'bulkDelete': $dbForProject->deleteDocuments( - $collectionName, - $data['queries'] ?? [] + $collectionId, + Query::parseQueries($data['queries'] ?? []) ); break; } From 5cd99de6e4ec969291de7f42b084f13d57e7110c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:36:59 +1200 Subject: [PATCH 077/385] Remove redundant read --- .../Platform/Modules/Databases/Http/Transactions/Update.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 419d83eb41..ee40bbc6cd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -187,7 +187,7 @@ class Update extends Action }); } - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committed', ])); @@ -203,8 +203,6 @@ class Update extends Action throw new Exception(Exception::TRANSACTION_CONFLICT); } }); - - $transaction = $dbForProject->getDocument('transactions', $transactionId); } if ($rollback) { From 9838571184f437b80a08d53b49419fef7c2c0a7a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:37:18 +1200 Subject: [PATCH 078/385] Catch explicit txn exception --- .../Platform/Modules/Databases/Http/Transactions/Update.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index ee40bbc6cd..d42e242e82 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -201,6 +201,12 @@ class Update extends Action ])); throw new Exception(Exception::TRANSACTION_CONFLICT); + } catch (TransactionException $e) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + + throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); } }); } From dfeadfd50f24885b3336e30c3ee182d68f8851d1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:37:29 +1200 Subject: [PATCH 079/385] Update naming --- .../Databases/Http/Transactions/Update.php | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index d42e242e82..27251f3fd9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Transactions; +use Appwrite\Event\Delete; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; @@ -13,6 +14,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; @@ -102,40 +104,37 @@ class Update extends Action foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; + $collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; $documentId = $operation['documentId']; + $createdAt = new \DateTime($operation['$createdAt']); $action = $operation['action']; $data = $operation['data']; - $operationCreatedAt = new \DateTime($operation['$createdAt']); - - $collectionName = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; // Wrap each operation with the timestamp from when it was logged - $dbForProject->withRequestTimestamp($operationCreatedAt, function () use ($dbForProject, $action, $collectionName, $documentId, $data) { + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $queueForDeletes, $action, $collectionId, $documentId, $data) { switch ($action) { case 'create': - $document = new Document([ - '$id' => $documentId ?? ID::unique(), - ...$data - ]); - $dbForProject->createDocument($collectionName, $document); + $document = new Document($data); + $dbForProject->createDocument($collectionId, $document); break; - + case 'update': + $document = new Document($data); + $dbForProject->updateDocument($collectionId, $documentId, $document); + break; + case 'upsert': - $document = new Document([ - '$id' => $documentId, - ...$data, - ]); - $dbForProject->createOrUpdateDocument($collectionName, $document); + $document = new Document($data); + $dbForProject->createOrUpdateDocuments($collectionId, [$document]); break; case 'delete': - $dbForProject->deleteDocument($collectionName, $documentId); + $dbForProject->deleteDocument($collectionId, $documentId); break; case 'increment': $dbForProject->increaseDocumentAttribute( - collection: $collectionName, + collection: $collectionId, id: $documentId, attribute: $data['attribute'], value: $data['value'] ?? 1, @@ -145,7 +144,7 @@ class Update extends Action case 'decrement': $dbForProject->decreaseDocumentAttribute( - collection: $collectionName, + collection: $collectionId, id: $documentId, attribute: $data['attribute'], value: $data['value'] ?? 1, From 6e6364638dcef2b5e28f2ca31e31b9efec13e374 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:37:39 +1200 Subject: [PATCH 080/385] Add missing validator ops --- src/Appwrite/Utopia/Database/Validator/Operation.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 989fd76eec..17a2839f59 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -21,7 +21,9 @@ class Operation extends Validator 'update', 'upsert', 'delete', + 'bulkCreate', 'bulkUpdate', + 'bulkUpsert', 'bulkDelete', ]; From 2a5ab1f8aae86eae30f30c94917e46cb49e2f870 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:48:37 +1200 Subject: [PATCH 081/385] Update src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Utopia/Database/Validator/Queries/Transactions.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php index ab3e933d6f..8a9604a7ae 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php @@ -4,7 +4,8 @@ namespace Appwrite\Utopia\Database\Validator\Queries; class Transactions extends Base { - public const array ALLOWED_ATTRIBUTES = [ + /** @var string[] */ + public const ALLOWED_ATTRIBUTES = [ 'status', 'expiresAt', ]; From 875338d133273c04acb111e615011782a6e32ca2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 00:48:56 +1200 Subject: [PATCH 082/385] Update src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php index 8a9604a7ae..2f557e5489 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php @@ -12,6 +12,6 @@ class Transactions extends Base public function __construct() { - parent::__construct('functions', self::ALLOWED_ATTRIBUTES); + parent::__construct('transactions', self::ALLOWED_ATTRIBUTES); } } From e9c730e0c1ed267d90a70a3d694e6502158cfddd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:03:18 +1200 Subject: [PATCH 083/385] Add txn id to create multi-method params --- .../Databases/Http/Databases/Collections/Documents/Create.php | 2 ++ .../Utopia/Database/Validator/Queries/Transactions.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 5e7c20b76d..6c9a7ee228 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 @@ -82,6 +82,7 @@ class Create extends Action new Parameter('documentId', optional: false), new Parameter('data', optional: false), new Parameter('permissions', optional: true), + new Parameter('transactionId', optional: true), ], deprecated: new Deprecated( since: '1.8.0', @@ -106,6 +107,7 @@ class Create extends Action new Parameter('databaseId', optional: false), new Parameter('collectionId', optional: false), new Parameter('documents', optional: false), + new Parameter('transactionId', optional: true), ], deprecated: new Deprecated( since: '1.8.0', diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php index 2f557e5489..b49494c0af 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Transactions.php @@ -4,8 +4,8 @@ namespace Appwrite\Utopia\Database\Validator\Queries; class Transactions extends Base { - /** @var string[] */ - public const ALLOWED_ATTRIBUTES = [ + /** @var array */ + public const array ALLOWED_ATTRIBUTES = [ 'status', 'expiresAt', ]; From 56d7716ddb1998ea01a3300f59e7d6a755152d86 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:03:36 +1200 Subject: [PATCH 084/385] Require data param for ops --- src/Appwrite/Utopia/Database/Validator/Operation.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 17a2839f59..8ef3817668 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -13,6 +13,7 @@ class Operation extends Validator 'databaseId', 'collectionId', 'action', + 'data', ]; /** @var array */ From 0533568f9e6ad385c123acac5839b9ec6937183b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:03:46 +1200 Subject: [PATCH 085/385] Fix test --- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 11d64077d9..ddc0af99c7 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -6195,10 +6195,10 @@ trait DatabasesBase $this->assertEquals(200, $rollback['headers']['status-code']); $this->assertEquals('rolledBack', $rollback['body']['status']); - $documents = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/collections/$collectionId/documents", [ + $documents = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/collections/$collectionId/documents", \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()); + ], $this->getHeaders())); $this->assertEquals(0, count($documents['body']['documents'])); } @@ -6260,7 +6260,7 @@ trait DatabasesBase 'databaseId' => $databaseId, 'collectionId' => $collectionId, 'action' => 'create', - 'data' => ['attribute' => 'value'], + 'data' => ['name' => 'value'], ] ] ]); From bed46be72005bf6b34b44c3841e10e9973c904e8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:05:03 +1200 Subject: [PATCH 086/385] Add grids params --- .../Modules/Databases/Http/Grids/Tables/Rows/Create.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php index db6029def9..dbebfd28c4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php @@ -69,6 +69,7 @@ class Create extends DocumentCreate new Parameter('rowId', optional: false), new Parameter('data', optional: false), new Parameter('permissions', optional: true), + new Parameter('transactionId', optional: true), ] ), new Method( @@ -89,6 +90,7 @@ class Create extends DocumentCreate new Parameter('databaseId', optional: false), new Parameter('tableId', optional: false), new Parameter('rows', optional: false), + new Parameter('transactionId', optional: true), ] ) ]) @@ -97,7 +99,7 @@ class Create extends DocumentCreate ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tables#tablesCreate). Make sure to define columns before creating rows.') ->param('data', [], new JSON(), 'Row data as JSON object.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('rows', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) + ->param('rows', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of rows data as JSON objects.', true, ['plan']) ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) ->inject('response') ->inject('dbForProject') From ad8cebc6e3daf9f8cd8045e2a7570ec62d9c2a4f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:32:09 +1200 Subject: [PATCH 087/385] Ensure updated txn is returned from bulk op add --- .../Modules/Databases/Http/Transactions/Operations/Create.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php index 4974556cec..91631fc257 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php @@ -100,9 +100,9 @@ class Create extends Action ]); } - $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { + $transaction = $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { $dbForProject->createDocuments('transactionLogs', $staged); - $dbForProject->increaseDocumentAttribute( + return $dbForProject->increaseDocumentAttribute( 'transactions', $transactionId, 'operations', From 46f8d76d7a47fd2284a14da0335e358d442d6000 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:32:21 +1200 Subject: [PATCH 088/385] Fix defualts for bulk op add --- .../Modules/Databases/Http/Transactions/Operations/Create.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php index 91631fc257..74f6337751 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php @@ -94,9 +94,9 @@ class Create extends Action 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $operation['documentId'] ?? ID::unique(), + 'documentId' => $operation['documentId'] ?? null, 'action' => $operation['action'], - 'data' => $operation['data'] ?? new \stdClass(), + 'data' => $operation['data'] ?? [], ]); } From 2b3795224d707756059b3c58eb3abfcc3d5c1e90 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:33:22 +1200 Subject: [PATCH 089/385] Check for documentId on ops that require it --- src/Appwrite/Utopia/Database/Validator/Operation.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 8ef3817668..ac585e26bc 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -71,6 +71,15 @@ class Operation extends Validator return false; } + // If action requires documentId, it must be present + if ( + isset($this->requiresDocumentId[$value['action']]) && + !\array_key_exists('documentId', $value) + ) { + $this->description = "Key 'documentId' is required for action '{$value['action']}'"; + return false; + } + // Data must be array (can be empty) if (!\is_array($value['data'])) { $this->description = "Key 'data' must be an array"; From d444bb66b87aef4761a49a81897ef8298e09d6a3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:33:35 +1200 Subject: [PATCH 090/385] Truemaps for fast lookup --- .../Utopia/Database/Validator/Operation.php | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index ac585e26bc..25b9adcd9a 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -16,16 +16,24 @@ class Operation extends Validator 'data', ]; - /** @var array */ + /** @var array */ + private array $requiresDocumentId = [ + 'create' => true, + 'update' => true, + 'upsert' => true, + 'delete' => true, + ]; + + /** @var array */ private array $actions = [ - 'create', - 'update', - 'upsert', - 'delete', - 'bulkCreate', - 'bulkUpdate', - 'bulkUpsert', - 'bulkDelete', + 'create' => true, + 'update' => true, + 'upsert' => true, + 'delete' => true, + 'bulkCreate' => true, + 'bulkUpdate' => true, + 'bulkUpsert' => true, + 'bulkDelete' => true, ]; public function getDescription(): string @@ -66,7 +74,7 @@ class Operation extends Validator } // Validate action - if (!\in_array($value['action'], $this->actions, true)) { + if (!isset($this->actions[$value['action']])) { $this->description = "Key 'action' must be one of: " . \implode(', ', $this->actions); return false; } From 44daaae981c14147cf14d012137cd1fa8828793b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:33:44 +1200 Subject: [PATCH 091/385] Update deps --- composer.lock | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/composer.lock b/composer.lock index b97dbb2e7f..a0d261c518 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": "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 +3595,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", @@ -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", @@ -8397,18 +8397,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-transaction-pinning", - "alias": "0.71.6", - "alias_normalized": "0.71.6.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From 4b4969e79b3ce457a37c0f4710192bb8ca6a5f3f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:40:08 +1200 Subject: [PATCH 092/385] Sync response --- src/Appwrite/Utopia/Response.php | 188 +++++++++---------------------- 1 file changed, 54 insertions(+), 134 deletions(-) diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 1e8f9d5ee8..53f38b9016 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -161,6 +161,7 @@ class Response extends SwooleResponse public const MODEL_METRIC_LIST = 'metricList'; public const MODEL_METRIC_BREAKDOWN = 'metricBreakdown'; public const MODEL_ERROR_DEV = 'errorDev'; + public const MODEL_BASE_LIST = 'baseList'; public const MODEL_USAGE_DATABASES = 'usageDatabases'; public const MODEL_USAGE_DATABASE = 'usageDatabase'; public const MODEL_USAGE_TABLE = 'usageTable'; @@ -378,6 +379,13 @@ class Response extends SwooleResponse // Console public const MODEL_CONSOLE_VARIABLES = 'consoleVariables'; + // Deprecated + public const MODEL_PERMISSIONS = 'permissions'; + public const MODEL_RULE = 'rule'; + public const MODEL_TASK = 'task'; + public const MODEL_DOMAIN = 'domain'; + public const MODEL_DOMAIN_LIST = 'domainList'; + // Tests (keep last) public const MODEL_MOCK = 'mock'; @@ -441,56 +449,29 @@ class Response extends SwooleResponse ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) - ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) - ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) - ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET)) - ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) - ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) - ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) - ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) - ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) - ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT)) ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) - ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) - ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION)) - ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) - ->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK)) - ->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK)) - ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) - ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) - ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) - ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) - ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) - ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) - ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) - ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) - ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) - ->setModel(new BaseList('Message List', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) - ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) - ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) - ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) - ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) + ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) - ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) - ->setModel(new BaseList('Provider List', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) - ->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN)) - ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) - ->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME)) - ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME)) - ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) - ->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE)) - ->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE)) - ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) + ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) + ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) + ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) + ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) + ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) + ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) + ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) + ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) + ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) + ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) + ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) + ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) - ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) - ->setModel(new BaseList('Topic List', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) ->setModel(new BaseList('Transaction List', self::MODEL_TRANSACTION_LIST, 'transactions', self::MODEL_TRANSACTION)) - ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) + ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) + ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) + ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) - ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) - ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) // Entities ->setModel(new Database()) // Collection API Models @@ -531,75 +512,30 @@ class Response extends SwooleResponse ->setModel(new AlgoSha()) ->setModel(new AlgoPhpass()) ->setModel(new AlgoBcrypt()) - ->setModel(new AlgoMd5()) - ->setModel(new AlgoPhpass()) ->setModel(new AlgoScrypt()) ->setModel(new AlgoScryptModified()) - ->setModel(new AlgoSha()) - ->setModel(new Attribute()) - ->setModel(new AttributeBoolean()) - ->setModel(new AttributeDatetime()) - ->setModel(new AttributeEmail()) - ->setModel(new AttributeEnum()) - ->setModel(new AttributeFloat()) - ->setModel(new AttributeIP()) - ->setModel(new AttributeInteger()) - ->setModel(new AttributeList()) - ->setModel(new AttributeRelationship()) - ->setModel(new AttributeString()) - ->setModel(new AttributeURL()) - ->setModel(new AuthProvider()) - ->setModel(new Branch()) - ->setModel(new Bucket()) - ->setModel(new Collection()) - ->setModel(new ConsoleVariables()) - ->setModel(new Continent()) - ->setModel(new Country()) - ->setModel(new Currency()) - ->setModel(new Database()) - ->setModel(new Deployment()) - ->setModel(new DetectionFramework()) - ->setModel(new DetectionRuntime()) - ->setModel(new DevKey()) - ->setModel(new Execution()) - ->setModel(new File()) - ->setModel(new Framework()) - ->setModel(new FrameworkAdapter()) - ->setModel(new Func()) - ->setModel(new Headers()) - ->setModel(new HealthAntivirus()) - ->setModel(new HealthCertificate()) - ->setModel(new HealthQueue()) - ->setModel(new HealthStatus()) - ->setModel(new HealthTime()) - ->setModel(new HealthVersion()) + ->setModel(new AlgoArgon2()) + ->setModel(new Account()) + ->setModel(new Preferences()) + ->setModel(new Session()) ->setModel(new Identity()) - ->setModel(new Index()) - ->setModel(new Installation()) + ->setModel(new Token()) ->setModel(new JWT()) - ->setModel(new Key()) - ->setModel(new Language()) ->setModel(new Locale()) ->setModel(new LocaleCode()) - ->setModel(new Log()) - ->setModel(new MFAChallenge()) - ->setModel(new MFAFactors()) - ->setModel(new MFARecoveryCodes()) - ->setModel(new MFAType()) + ->setModel(new File()) + ->setModel(new Bucket()) + ->setModel(new ResourceToken()) + ->setModel(new Team()) ->setModel(new Membership()) - ->setModel(new Message()) - ->setModel(new Metric()) - ->setModel(new MetricBreakdown()) - ->setModel(new Migration()) - ->setModel(new MigrationFirebaseProject()) - ->setModel(new MigrationReport()) - ->setModel(new MockNumber()) - ->setModel(new ModelDocument()) - ->setModel(new Phone()) - ->setModel(new Platform()) - ->setModel(new Preferences()) - ->setModel(new Project()) - ->setModel(new Provider()) + ->setModel(new Site()) + ->setModel(new TemplateSite()) + ->setModel(new TemplateFramework()) + ->setModel(new Func()) + ->setModel(new TemplateFunction()) + ->setModel(new TemplateRuntime()) + ->setModel(new TemplateVariable()) + ->setModel(new Installation()) ->setModel(new ProviderRepository()) ->setModel(new ProviderRepositoryFramework()) ->setModel(new ProviderRepositoryRuntime()) @@ -648,38 +584,22 @@ class Response extends SwooleResponse ->setModel(new Headers()) ->setModel(new Specification()) ->setModel(new Rule()) - ->setModel(new Runtime()) - ->setModel(new Session()) - ->setModel(new Site()) - ->setModel(new Specification()) - ->setModel(new Subscriber()) - ->setModel(new Target()) - ->setModel(new Team()) - ->setModel(new TemplateEmail()) - ->setModel(new TemplateFramework()) - ->setModel(new TemplateFunction()) - ->setModel(new TemplateRuntime()) ->setModel(new TemplateSMS()) - ->setModel(new TemplateSite()) - ->setModel(new TemplateVariable()) - ->setModel(new Token()) + ->setModel(new TemplateEmail()) + ->setModel(new ConsoleVariables()) + ->setModel(new MFAChallenge()) + ->setModel(new MFARecoveryCodes()) + ->setModel(new MFAType()) + ->setModel(new MFAFactors()) + ->setModel(new Provider()) + ->setModel(new Message()) ->setModel(new Topic()) ->setModel(new Transaction()) - ->setModel(new UsageBuckets()) - ->setModel(new UsageCollection()) - ->setModel(new UsageDatabase()) - ->setModel(new UsageDatabases()) - ->setModel(new UsageFunction()) - ->setModel(new UsageFunctions()) - ->setModel(new UsageProject()) - ->setModel(new UsageSite()) - ->setModel(new UsageSites()) - ->setModel(new UsageStorage()) - ->setModel(new UsageUsers()) - ->setModel(new User()) - ->setModel(new Variable()) - ->setModel(new VcsContent()) - ->setModel(new Webhook()) + ->setModel(new Subscriber()) + ->setModel(new Target()) + ->setModel(new Migration()) + ->setModel(new MigrationReport()) + ->setModel(new MigrationFirebaseProject()) // Tests (keep last) ->setModel(new Mock()); From ef8d1b525d2761da3f6efa517084feac93b2efda Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 01:54:45 +1200 Subject: [PATCH 093/385] Fix missing grids injections --- .../Databases/Http/Grids/Tables/Rows/Column/Decrement.php | 1 + .../Databases/Http/Grids/Tables/Rows/Column/Increment.php | 1 + .../Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php | 1 + .../Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php | 1 + .../Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php | 1 + .../Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php | 1 + 6 files changed, 6 insertions(+) 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 cb04e118ca..c5bcf5015e 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 @@ -65,6 +65,7 @@ class Decrement extends DecrementDocumentAttribute ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } } 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 4e36a2b912..0902a850a7 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 @@ -65,6 +65,7 @@ class Increment extends IncrementDocumentAttribute ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php index dbebfd28c4..cf0adddb89 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Create.php @@ -109,6 +109,7 @@ class Create extends DocumentCreate ->inject('queueForRealtime') ->inject('queueForFunctions') ->inject('queueForWebhooks') + ->inject('plan') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php index b13b17b8cd..ca1914b422 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Delete.php @@ -67,6 +67,7 @@ class Delete extends DocumentDelete ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php index c6ce1a9795..bed6c549f9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Update.php @@ -66,6 +66,7 @@ class Update extends DocumentUpdate ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php index 157956ca18..daf9147390 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Grids/Tables/Rows/Upsert.php @@ -69,6 +69,7 @@ class Upsert extends DocumentUpsert ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('plan') ->callback($this->action(...)); } } From 237263a194fc68385bbc0f56e54d496735abbf6e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Aug 2025 02:01:44 +1200 Subject: [PATCH 094/385] Lint --- .../Collections/Documents/Attribute/Decrement.php | 4 ++-- .../Databases/Collections/Documents/Bulk/Update.php | 2 +- .../Http/Databases/Collections/Documents/Create.php | 4 ++-- .../Modules/Databases/Http/Transactions/Create.php | 2 +- .../Modules/Databases/Http/Transactions/Delete.php | 2 +- .../Modules/Databases/Http/Transactions/Get.php | 2 +- .../Databases/Http/Transactions/Operations/Create.php | 2 +- .../Modules/Databases/Http/Transactions/Update.php | 11 +++++------ .../Modules/Databases/Http/Transactions/XList.php | 2 +- 9 files changed, 15 insertions(+), 16 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 1a6a7477af..7f67889a1c 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 @@ -87,12 +87,12 @@ class Decrement extends Action public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { - $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); if ($collection->isEmpty()) { throw new Exception($this->getParentNotFoundException()); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index caaeaced1a..c198e9cc7b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -109,7 +109,7 @@ class Update extends Action $hasRelationships = \array_filter( $collection->getAttribute('attributes', []), - fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); if ($hasRelationships) { 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 6c9a7ee228..d4a7e2cfda 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 @@ -402,7 +402,7 @@ class Create extends Action 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $isBulk ? null: $documentId, + 'documentId' => $isBulk ? null : $documentId, 'action' => $isBulk ? 'bulkCreate' : 'create', 'data' => $isBulk ? $documents : $documents[0], ]); @@ -476,7 +476,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), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php index 25a16e19e3..2c98565568 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php @@ -69,4 +69,4 @@ class Create extends Action ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php index ae6f41fb2d..511f1140bd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Delete.php @@ -73,4 +73,4 @@ class Delete extends Action $response->noContent(); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php index 1a8286e658..12b8629b6a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Get.php @@ -66,4 +66,4 @@ class Get extends Action ->setStatusCode(SwooleResponse::STATUS_CODE_OK) ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php index 74f6337751..eac2b930eb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Operations/Create.php @@ -114,4 +114,4 @@ class Create extends Action ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 27251f3fd9..4775fd06c9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -15,7 +15,6 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Transaction as TransactionException; -use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; @@ -127,11 +126,11 @@ class Update extends Action $document = new Document($data); $dbForProject->createOrUpdateDocuments($collectionId, [$document]); break; - + case 'delete': $dbForProject->deleteDocument($collectionId, $documentId); break; - + case 'increment': $dbForProject->increaseDocumentAttribute( collection: $collectionId, @@ -141,7 +140,7 @@ class Update extends Action max: $data['max'] ?? null ); break; - + case 'decrement': $dbForProject->decreaseDocumentAttribute( collection: $collectionId, @@ -175,7 +174,7 @@ class Update extends Action } $dbForProject->createOrUpdateDocuments($collectionId, $documents); break; - + case 'bulkDelete': $dbForProject->deleteDocuments( $collectionId, @@ -224,4 +223,4 @@ class Update extends Action ->setStatusCode(SwooleResponse::STATUS_CODE_OK) ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php index ee58807d6f..bf8a33a793 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/XList.php @@ -70,4 +70,4 @@ class XList extends Action 'total' => $dbForProject->count('transactions', $queries), ]), UtopiaResponse::MODEL_TRANSACTION_LIST); } -} \ No newline at end of file +} From 14002dc20d38044ee9f7dcb67b3695aaacf9ebef Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 18 Aug 2025 17:37:16 +1200 Subject: [PATCH 095/385] Remove invalid return --- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 580f9144b6..d652673370 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4349,8 +4349,6 @@ trait DatabasesBase $this->assertEquals($document['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); } - - return $data; } public function testUpdatePermissionsWithEmptyPayload(): array From c9cf38d6304bac6edf5ec62bdf0e7617f915abc5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 01:31:53 +1200 Subject: [PATCH 096/385] Add expired log deletion --- app/init/constants.php | 1 + composer.lock | 169 +++++++++++++----- .../Databases/Http/Transactions/Update.php | 1 + src/Appwrite/Platform/Workers/Deletes.php | 80 ++++++--- 4 files changed, 183 insertions(+), 68 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index 66933149a8..2e22a4ca3c 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -109,6 +109,7 @@ const DELETE_TYPE_DATABASES = 'databases'; const DELETE_TYPE_DOCUMENT = 'document'; const DELETE_TYPE_COLLECTIONS = 'collections'; const DELETE_TYPE_TRANSACTION = 'transaction'; +const DELETE_TYPE_EXPIRED_TRANSACTIONS = 'expired_transactions'; const DELETE_TYPE_PROJECTS = 'projects'; const DELETE_TYPE_SITES = 'sites'; const DELETE_TYPE_FUNCTIONS = 'functions'; diff --git a/composer.lock b/composer.lock index 3bf17bc228..90bb2f12f6 100644 --- a/composer.lock +++ b/composer.lock @@ -2599,16 +2599,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "1c064a0c67749923483216b081066642751cc2c7" + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7", - "reference": "1c064a0c67749923483216b081066642751cc2c7", + "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", "shasum": "" }, "require": { @@ -2616,6 +2616,7 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -2674,7 +2675,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.2" + "source": "https://github.com/symfony/http-client/tree/v7.3.3" }, "funding": [ { @@ -2694,7 +2695,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-08-27T07:45:05+00:00" }, { "name": "symfony/http-client-contracts", @@ -2939,6 +2940,86 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -3557,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "1.2.3", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "8a536fead840d9da6ee819fe6b80e0f047997f69" + "reference": "87fb55e86892eecd726635eb1829acb743c2c156" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/8a536fead840d9da6ee819fe6b80e0f047997f69", - "reference": "8a536fead840d9da6ee819fe6b80e0f047997f69", + "url": "https://api.github.com/repos/utopia-php/database/zipball/87fb55e86892eecd726635eb1829acb743c2c156", + "reference": "87fb55e86892eecd726635eb1829acb743c2c156", "shasum": "" }, "require": { @@ -3607,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.2.3" + "source": "https://github.com/utopia-php/database/tree/1.2.4" }, - "time": "2025-08-27T11:47:04+00:00" + "time": "2025-09-01T06:01:09+00:00" }, { "name": "utopia-php/detector", @@ -4109,16 +4190,16 @@ }, { "name": "utopia-php/migration", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569" + "reference": "38171023efd3abe650d2abc5ac65f5df52311da6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/0e4499d9dd2c90c2be188cc5fb7a32d9a892b569", - "reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6", + "reference": "38171023efd3abe650d2abc5ac65f5df52311da6", "shasum": "" }, "require": { @@ -4159,9 +4240,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.0.0" + "source": "https://github.com/utopia-php/migration/tree/1.0.1" }, - "time": "2025-08-13T09:15:53+00:00" + "time": "2025-08-28T13:41:25+00:00" }, { "name": "utopia-php/orchestration", @@ -7410,16 +7491,16 @@ }, { "name": "symfony/console", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { @@ -7484,7 +7565,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.2" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -7504,7 +7585,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:13:41+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/filesystem", @@ -7646,16 +7727,16 @@ }, { "name": "symfony/options-resolver", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", - "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { @@ -7693,7 +7774,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -7713,7 +7794,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/polyfill-ctype", @@ -8047,16 +8128,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -8088,7 +8169,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -8099,25 +8180,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/string", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { @@ -8175,7 +8260,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -8195,7 +8280,7 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "textalk/websocket", diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 4775fd06c9..dfae4f3a50 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -193,6 +193,7 @@ class Update extends Action $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); + } catch (DuplicateException|ConflictException) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index c7dda8ccf0..b68c758169 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -86,14 +86,15 @@ class Deletes extends Action string $executionRetention, string $auditRetention, Log $log - ): void { + ): void + { $payload = $message->getPayload() ?? []; if (empty($payload)) { throw new Exception('Missing payload'); } - $type = $payload['type'] ?? ''; + $type = $payload['type'] ?? ''; $datetime = $payload['datetime'] ?? null; $hourlyUsageRetentionDatetime = $payload['hourlyUsageRetentionDatetime'] ?? null; $resource = $payload['resource'] ?? null; @@ -185,6 +186,7 @@ class Deletes extends Action $this->deleteAuditLogs($project, $getProjectDB, $auditRetention); $this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime); $this->deleteExpiredSessions($project, $getProjectDB); + $this->deleteExpiredTransactions($project, $getProjectDB); break; default: throw new \Exception('No delete operation for type: ' . \strval($type)); @@ -308,9 +310,9 @@ class Deletes extends Action * @param Document $project * @param callable $getProjectDB * @param string $resource + * @param string|null $resourceType * @return void * @throws Authorization - * @param string|null $resourceType * @throws Exception */ private function deleteCacheByResource(Document $project, callable $getProjectDB, string $resource, string $resourceType = null): void @@ -398,7 +400,7 @@ class Deletes extends Action */ private function deleteUsageStats(Document $project, callable $getProjectDB, callable $getLogsDB, string $hourlyUsageRetentionDatetime): void { - /** @var Database $dbForProject*/ + /** @var Database $dbForProject */ $dbForProject = $getProjectDB($project); $selects = [...$this->selects, 'time']; @@ -413,7 +415,7 @@ class Deletes extends Action ], $dbForProject); if ($project->getId() !== 'console') { - /** @var Database $dbForLogs*/ + /** @var Database $dbForLogs */ $dbForLogs = call_user_func($getLogsDB, $project); // Delete Usage stats from logsDB @@ -455,16 +457,16 @@ class Deletes extends Action } /** - * @param Database $dbForPlatform - * @param Document $document - * @return void - * @throws Authorization - * @throws DatabaseException - * @throws Conflict - * @throws Restricted - * @throws Structure - * @throws Exception - */ + * @param Database $dbForPlatform + * @param Document $document + * @return void + * @throws Authorization + * @throws DatabaseException + * @throws Conflict + * @throws Restricted + * @throws Structure + * @throws Exception + */ protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void { @@ -542,7 +544,7 @@ class Deletes extends Action ); } } catch (Throwable $e) { - Console::error('Error deleting '.$collection->getId().' '.$e->getMessage()); + Console::error('Error deleting ' . $collection->getId() . ' ' . $e->getMessage()); } }); @@ -609,7 +611,7 @@ class Deletes extends Action ); } elseif ($sharedTablesV2) { $queries = \array_map( - fn ($id) => Query::notEqual('$id', $id), + fn($id) => Query::notEqual('$id', $id), $projectCollectionIds ); @@ -953,14 +955,14 @@ class Deletes extends Action } Console::info("Deleting screenshots for deployment " . $deployment->getId()); - $bucket = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')); + $bucket = ValidatorAuthorization::skip(fn() => $dbForPlatform->getDocument('buckets', 'screenshots')); if ($bucket->isEmpty()) { Console::error('Failed to get bucket for deployment screenshots'); return; } foreach ($screenshotIds as $id) { - $file = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $id)); + $file = ValidatorAuthorization::skip(fn() => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $id)); if ($file->isEmpty()) { Console::error('Failed to get deployment screenshot: ' . $id); @@ -1117,12 +1119,10 @@ class Deletes extends Action array $queries, Database $database, ?callable $callback = null - ): void { + ): void + { $start = \microtime(true); - $deleteBatchSize = Database::DELETE_BATCH_SIZE; - $deleteBatchSize = 500; // TODO: Set right value in DB library after investigation - /** * deleteDocuments uses a cursor, we need to add a unique order by field or use default */ @@ -1130,11 +1130,10 @@ class Deletes extends Action $count = $database->deleteDocuments( $collection, $queries, - $deleteBatchSize, - $callback + onNext: $callback ); } catch (Throwable $th) { - $tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : ''; + $tenant = $database->getSharedTables() ? 'Tenant:' . $database->getTenant() : ''; Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collection} {$tenant} :{$th->getMessage()}"); return; } @@ -1318,4 +1317,33 @@ class Deletes extends Action Console::error("Failed to delete transaction logs for {$transactionId}: " . $th->getMessage()); } } + + private function deleteExpiredTransactions(Document $project, callable $getProjectDB): void + { + $dbForProject = $getProjectDB($project); + $transactionInternalIds = []; + + try { + $dbForProject->deleteDocuments('transactions', [ + Query::equal('status', ['pending']), + Query::lessThan('expiresAt', DateTime::format(new \DateTime())), + ], onNext: function (Document $transaction) use ($dbForProject, $project, &$transactionInternalIds) { + $transactionInternalIds[] = $transaction->getSequence(); + }, onError: function (Throwable $th) use ($project) { + // Swallow errors to avoid breaking the cleanup process + }); + } catch (Throwable $th) { + Console::error("Failed to find expired transactions for project {$project->getId()}: " . $th->getMessage()); + } + + if (empty($transactionInternalIds)) { + return; + } + + $dbForProject->deleteDocuments('transactionLogs', [ + Query::equal('transactionInternalId', $transactionInternalIds), + ], onError: function (Throwable $th) use ($project) { + // Swallow errors to avoid breaking the cleanup process + }); + } } From 3f0ad7f6c76d9da9bd3bbba6f8ce48ae9736aabb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 03:41:53 +1200 Subject: [PATCH 097/385] Update registry --- .../Modules/Databases/Services/Http.php | 12 +++---- .../Databases/Services/Registry/Databases.php | 31 ------------------- .../Registry/{Collections.php => Legacy.php} | 23 +++++++++++++- .../Registry/{Tables.php => TablesDB.php} | 2 +- .../Services/Registry/Transactions.php | 27 ++++++++++++++++ 5 files changed, 56 insertions(+), 39 deletions(-) delete mode 100644 src/Appwrite/Platform/Modules/Databases/Services/Registry/Databases.php rename src/Appwrite/Platform/Modules/Databases/Services/Registry/{Collections.php => Legacy.php} (87%) rename src/Appwrite/Platform/Modules/Databases/Services/Registry/{Tables.php => TablesDB.php} (99%) create mode 100644 src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Http.php b/src/Appwrite/Platform/Modules/Databases/Services/Http.php index ccd9dfd140..e5a2d92b40 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Http.php @@ -3,9 +3,9 @@ namespace Appwrite\Platform\Modules\Databases\Services; use Appwrite\Platform\Modules\Databases\Http\Init\Timeout; -use Appwrite\Platform\Modules\Databases\Services\Registry\Collections as CollectionsRegistry; -use Appwrite\Platform\Modules\Databases\Services\Registry\Databases as DatabasesRegistry; -use Appwrite\Platform\Modules\Databases\Services\Registry\Tables as TablesRegistry; +use Appwrite\Platform\Modules\Databases\Services\Registry\Legacy as LegacyRegistry; +use Appwrite\Platform\Modules\Databases\Services\Registry\TablesDB as TablesDBRegistry; +use Appwrite\Platform\Modules\Databases\Services\Registry\Transactions as TransactionsRegistry; use Utopia\Platform\Service; class Http extends Service @@ -17,9 +17,9 @@ class Http extends Service $this->addAction(Timeout::getName(), new Timeout()); foreach ([ - DatabasesRegistry::class, - CollectionsRegistry::class, - TablesRegistry::class, + LegacyRegistry::class, + TablesDBRegistry::class, + TransactionsRegistry::class, ] as $registrar) { new $registrar($this); } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Databases.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Databases.php deleted file mode 100644 index 81c9174253..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Databases.php +++ /dev/null @@ -1,31 +0,0 @@ -addAction(CreateDatabase::getName(), new CreateDatabase()); - $service->addAction(GetDatabase::getName(), new GetDatabase()); - $service->addAction(UpdateDatabase::getName(), new UpdateDatabase()); - $service->addAction(DeleteDatabase::getName(), new DeleteDatabase()); - $service->addAction(ListDatabases::getName(), new ListDatabases()); - $service->addAction(ListDatabaseLogs::getName(), new ListDatabaseLogs()); - $service->addAction(GetDatabaseUsage::getName(), new GetDatabaseUsage()); - $service->addAction(ListDatabaseUsage::getName(), new ListDatabaseUsage()); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Collections.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php similarity index 87% rename from src/Appwrite/Platform/Modules/Databases/Services/Registry/Collections.php rename to src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php index bb0002ed47..ad51178197 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Collections.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php @@ -48,6 +48,14 @@ use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Logs\XList as use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Update as UpdateCollection; use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Usage\Get as GetCollectionUsage; use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\XList as ListCollections; +use Appwrite\Platform\Modules\Databases\Http\Databases\Create as CreateDatabase; +use Appwrite\Platform\Modules\Databases\Http\Databases\Delete as DeleteDatabase; +use Appwrite\Platform\Modules\Databases\Http\Databases\Get as GetDatabase; +use Appwrite\Platform\Modules\Databases\Http\Databases\Logs\XList as ListDatabaseLogs; +use Appwrite\Platform\Modules\Databases\Http\Databases\Update as UpdateDatabase; +use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\Get as GetDatabaseUsage; +use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\XList as ListDatabaseUsage; +use Appwrite\Platform\Modules\Databases\Http\Databases\XList as ListDatabases; use Utopia\Platform\Service; /** @@ -59,16 +67,29 @@ use Utopia\Platform\Service; * - Attributes * - Indexes */ -class Collections extends Base +class Legacy extends Base { protected function register(Service $service): void { + $this->registerDatabaseActions($service); $this->registerCollectionActions($service); $this->registerDocumentActions($service); $this->registerAttributeActions($service); $this->registerIndexActions($service); } + public function registerDatabaseActions(Service $service): void + { + $service->addAction(CreateDatabase::getName(), new CreateDatabase()); + $service->addAction(GetDatabase::getName(), new GetDatabase()); + $service->addAction(UpdateDatabase::getName(), new UpdateDatabase()); + $service->addAction(DeleteDatabase::getName(), new DeleteDatabase()); + $service->addAction(ListDatabases::getName(), new ListDatabases()); + $service->addAction(ListDatabaseLogs::getName(), new ListDatabaseLogs()); + $service->addAction(GetDatabaseUsage::getName(), new GetDatabaseUsage()); + $service->addAction(ListDatabaseUsage::getName(), new ListDatabaseUsage()); + } + private function registerCollectionActions(Service $service): void { $service->addAction(CreateCollection::getName(), new CreateCollection()); diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Tables.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php similarity index 99% rename from src/Appwrite/Platform/Modules/Databases/Services/Registry/Tables.php rename to src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php index 7772aff933..4d0456d9c0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Tables.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php @@ -66,7 +66,7 @@ use Utopia\Platform\Service; * - Columns * - Column-Indexes */ -class Tables extends Base +class TablesDB extends Base { protected function register(Service $service): void { diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php new file mode 100644 index 0000000000..3b02ffaec0 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php @@ -0,0 +1,27 @@ +addAction(CreateTransaction::getName(), new CreateTransaction()); + $service->addAction(GetTransaction::getName(), new GetTransaction()); + $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); + $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); + $service->addAction(ListTransactions::getName(), new ListTransactions()); + $service->addAction(CreateOperations::getName(), new CreateOperations()); + } +} \ No newline at end of file From 70ef7059c309db4d3e4d5738606116aa6f6cb33f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 03:42:19 +1200 Subject: [PATCH 098/385] Add increment validation --- .../Utopia/Database/Validator/Operation.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 25b9adcd9a..594648db3e 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -13,7 +13,6 @@ class Operation extends Validator 'databaseId', 'collectionId', 'action', - 'data', ]; /** @var array */ @@ -22,6 +21,8 @@ class Operation extends Validator 'update' => true, 'upsert' => true, 'delete' => true, + 'increment' => true, + 'decrement' => true, ]; /** @var array */ @@ -30,6 +31,8 @@ class Operation extends Validator 'update' => true, 'upsert' => true, 'delete' => true, + 'increment' => true, + 'decrement' => true, 'bulkCreate' => true, 'bulkUpdate' => true, 'bulkUpsert' => true, @@ -75,7 +78,7 @@ class Operation extends Validator // Validate action if (!isset($this->actions[$value['action']])) { - $this->description = "Key 'action' must be one of: " . \implode(', ', $this->actions); + $this->description = "Key 'action' must be one of: " . \implode(', ', array_keys($this->actions)); return false; } @@ -88,7 +91,11 @@ class Operation extends Validator return false; } - // Data must be array (can be empty) + // Data must be present and must be array (can be empty) + if (!\array_key_exists('data', $value)) { + $this->description = "Missing required key: data"; + return false; + } if (!\is_array($value['data'])) { $this->description = "Key 'data' must be an array"; return false; From 7861ee8cbda4eedcdfcea8b304607a63397e5f09 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 03:42:44 +1200 Subject: [PATCH 099/385] Add transaction scopes to test keys --- tests/e2e/Scopes/ProjectCustom.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index c2b4896814..49aabaae67 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -70,6 +70,8 @@ trait ProjectCustom 'databases.write', 'collections.read', 'collections.write', + 'transactions.read', + 'transactions.write', 'tables.read', 'tables.write', 'documents.read', From 04a9ff9c2305344fd172df6df3bf974a7137eee7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 03:42:56 +1200 Subject: [PATCH 100/385] Ensure ID on create doc op --- .../Platform/Modules/Databases/Http/Transactions/Update.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index dfae4f3a50..8282e61ef4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -88,7 +88,7 @@ class Update extends Action } if ($commit) { - $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, $transaction) { + $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, &$transaction) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', ])); @@ -113,6 +113,9 @@ class Update extends Action $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $queueForDeletes, $action, $collectionId, $documentId, $data) { switch ($action) { case 'create': + if ($documentId && !isset($data['$id'])) { + $data['$id'] = $documentId; + } $document = new Document($data); $dbForProject->createDocument($collectionId, $document); break; From 5528e9964bb76a03bc667da5327d64d5e22d84f4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 03:53:08 +1200 Subject: [PATCH 101/385] Updates tests --- .../Transactions/ACIDComplianceTest.php | 628 ++++++++++++++++++ .../DatabasesTransactionsTest.php | 363 ++++++++++ 2 files changed, 991 insertions(+) create mode 100644 tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php create mode 100644 tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php diff --git a/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php b/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php new file mode 100644 index 0000000000..4c0b0eecc3 --- /dev/null +++ b/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php @@ -0,0 +1,628 @@ +client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'AtomicityTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection with unique constraint + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'AtomicityTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add unique attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); + + // Add unique index + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'unique_email', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['email'] + ]); + + sleep(3); + + // Create first document outside transaction + $doc1 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'existing@example.com' + ] + ]); + + $this->assertEquals(201, $doc1['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed. Response: ' . json_encode($transaction)); + $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id. Response body: ' . json_encode($transaction['body'])); + $transactionId = $transaction['body']['$id']; + + // Add operations - second one will fail due to unique constraint + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'newuser@example.com' // This should succeed + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'existing@example.com' // This will fail - duplicate + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'anotheruser@example.com' // This should not be created due to atomicity + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code'], 'Add operations failed. Response: ' . json_encode($response['body'])); + + // Attempt to commit - should fail due to unique constraint violation + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + if ($response['headers']['status-code'] === 200) { + // If transaction succeeded, all documents should be created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have 4 documents total (1 original + 3 from transaction) + // But since we have a unique constraint violation, this might fail + $this->assertGreaterThanOrEqual(1, $documents['body']['total']); + } else { + $this->assertEquals(409, $response['headers']['status-code']); // Conflict error + + // Verify NO new documents were created (atomicity) + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(1, $documents['body']['total']); // Only the original document + $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + } + } + + /** + * Test consistency - schema validation and constraints + */ + public function testTransactionConsistency(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ConsistencyTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection with required fields and constraints + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'ConsistencyTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add required string attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'required_field', + 'size' => 256, + 'required' => true, + ]); + + // Add integer attribute with min/max constraints + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'required' => true, + 'min' => 18, + 'max' => 100 + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operations with both valid and invalid data + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Valid User', + 'age' => 25 // Valid age + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Too Young User', + 'age' => 10 // Below minimum - will fail constraint + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Another Valid User', + 'age' => 30 // Valid but should not be created due to transaction failure + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Attempt to commit - should fail due to constraint violation + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertContains($response['headers']['status-code'], [400, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body'])); + + // Verify no documents were created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(0, $documents['body']['total']); + } + + /** + * Test isolation - concurrent transactions on same data + */ + public function testTransactionIsolation(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IsolationTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'IsolationTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add counter attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000 + ]); + + sleep(2); + + // Create initial document with counter + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'shared_counter', + 'data' => [ + 'counter' => 0 + ] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create first transaction + $transaction1 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction1['headers']['status-code'], 'Transaction 1 creation should succeed'); + $this->assertArrayHasKey('$id', $transaction1['body'], 'Transaction 1 response should have $id'); + $transactionId1 = $transaction1['body']['$id']; + + // Create second transaction + $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction2['headers']['status-code'], 'Transaction 2 creation should succeed'); + $this->assertArrayHasKey('$id', $transaction2['body'], 'Transaction 2 response should have $id'); + $transactionId2 = $transaction2['body']['$id']; + + // Transaction 1: Increment counter by 10 + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId1}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'shared_counter', + 'action' => 'increment', + 'data' => [ + 'attribute' => 'counter', + 'value' => 10 + ] + ] + ] + ]); + + // Transaction 2: Increment counter by 5 + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'shared_counter', + 'action' => 'increment', + 'data' => [ + 'attribute' => 'counter', + 'value' => 5 + ] + ] + ] + ]); + + // Commit first transaction + $response1 = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId1}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response1['headers']['status-code']); + + // Commit second transaction + $response2 = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response2['headers']['status-code']); + + // Check final value - both increments should be applied + $document = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/shared_counter", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Both increments should be applied: 0 + 10 + 5 = 15 + $this->assertEquals(15, $document['body']['counter']); + } + + /** + * Test durability - committed data persists + */ + public function testTransactionDurability(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DurabilityTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'DurabilityTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create and commit transaction with multiple operations + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed'); + $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id'); + $transactionId = $transaction['body']['$id']; + + // Add multiple operations + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'durable_doc_1', + 'data' => [ + 'data' => 'Important data 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'durable_doc_2', + 'data' => [ + 'data' => 'Important data 2' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'durable_doc_1', + 'data' => [ + 'data' => 'Updated important data 1' + ] + ] + ] + ]); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body'])); + $this->assertEquals('committed', $response['body']['status']); + + // List all documents to see what was created + $allDocs = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertGreaterThan(0, $allDocs['body']['total'], 'Should have created documents. Found: ' . json_encode($allDocs['body'])); + + // Verify documents exist and have correct data + $document1 = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $document1['headers']['status-code']); + $this->assertEquals('Updated important data 1', $document1['body']['data']); + + $document2 = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/durable_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $document2['headers']['status-code']); + $this->assertEquals('Important data 2', $document2['body']['data']); + + // Further update outside transaction to ensure persistence + $update = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'data' => 'Modified outside transaction' + ] + ]); + + $this->assertEquals(200, $update['headers']['status-code']); + + // Verify the update persisted + $document1 = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Modified outside transaction', $document1['body']['data']); + + // List all documents to verify total count + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(2, $documents['body']['total']); + } +} \ No newline at end of file diff --git a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php new file mode 100644 index 0000000000..35340f8b0e --- /dev/null +++ b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php @@ -0,0 +1,363 @@ +client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Test creating a transaction with default TTL + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertArrayHasKey('$id', $response['body']); + $this->assertArrayHasKey('status', $response['body']); + $this->assertArrayHasKey('operations', $response['body']); + $this->assertArrayHasKey('expiresAt', $response['body']); + $this->assertEquals('pending', $response['body']['status']); + $this->assertEquals(0, $response['body']['operations']); + + $transactionId1 = $response['body']['$id']; + + // Test creating a transaction with custom TTL + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 900 + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals('pending', $response['body']['status']); + + $expiresAt = new \DateTime($response['body']['expiresAt']); + $now = new \DateTime(); + $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); + $this->assertGreaterThan(800, $diff); + $this->assertLessThan(1000, $diff); + + $transactionId2 = $response['body']['$id']; + + // Test invalid TTL values + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 30 // Below minimum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 4000 // Above maximum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + return [ + 'databaseId' => $databaseId, + 'transactionId1' => $transactionId1, + 'transactionId2' => $transactionId2 + ]; + } + + /** + * @depends testCreate + */ + public function testAddOperations(array $data): array + { + $databaseId = $data['databaseId']; + $transactionId = $data['transactionId1']; + + // Create a collection for testing + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionOperationsTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add attributes + $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + + // Wait for attribute to be created + sleep(2); + + // Add valid operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc1', + 'data' => [ + '$id' => 'doc1', + 'name' => 'Test Document 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc2', + 'data' => [ + '$id' => 'doc2', + 'name' => 'Test Document 2' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['operations']); + + // Test adding more operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Updated Document 1' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['operations']); + + // Test invalid database ID + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => 'invalid_database', + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Test invalid collection ID + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => 'invalid_collection', + 'action' => 'create', + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + return array_merge($data, [ + 'collectionId' => $collectionId + ]); + } + + /** + * @depends testAddOperations + */ + public function testCommit(array $data): void + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + $transactionId = $data['transactionId1']; + + // Commit the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('committed', $response['body']['status']); + + // Verify documents were created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(2, $documents['body']['total']); + + // Verify the update was applied + $doc1Found = false; + foreach ($documents['body']['documents'] as $doc) { + if ($doc['$id'] === 'doc1') { + $this->assertEquals('Updated Document 1', $doc['name']); + $doc1Found = true; + } + } + $this->assertTrue($doc1Found, 'Document doc1 should exist with updated name'); + + // Test committing already committed transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * @depends testCreate + */ + public function testRollback(array $data): void + { + $databaseId = $data['databaseId']; + $transactionId = $data['transactionId2']; + + // Create a collection for rollback test + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionRollbackTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Add operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'data' => [ + '$id' => 'rollback_doc', + 'value' => 'Should not exist' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Rollback the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('rolledBack', $response['body']['status']); + + // Verify no documents were created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(0, $documents['body']['total']); + } +} \ No newline at end of file From 2202dc9747864db0acf75a5f2215ab06955b8035 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 04:29:42 +1200 Subject: [PATCH 102/385] Handle intra-transactional ops with state tracking --- .../Databases/Http/Transactions/Update.php | 195 ++++++++++++------ .../Services/Registry/Transactions.php | 4 +- src/Appwrite/Platform/Workers/Deletes.php | 12 +- .../Transactions/ACIDComplianceTest.php | 8 +- .../DatabasesTransactionsTest.php | 42 ++-- 5 files changed, 171 insertions(+), 90 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 8282e61ef4..58ec470bd2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -93,13 +93,16 @@ class Update extends Action 'status' => 'committing', ])); - // Fetch operations ordered by sequence by default + // Fetch operations ordered by sequence by default to + // replay operations in exact order they were created $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), ]); + // Track transaction state for cross-operation visibility + $state = []; + try { - // Replay operations in exact order they were created foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; @@ -109,33 +112,28 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; - // Wrap each operation with the timestamp from when it was logged - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $queueForDeletes, $action, $collectionId, $documentId, $data) { + // Check if this operation depends on documents created in same transaction + $dependent = \in_array($action, ['update', 'increment', 'decrement']) + && isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Don't use timestamp wrapper for dependent operations switch ($action) { - case 'create': - if ($documentId && !isset($data['$id'])) { - $data['$id'] = $documentId; - } - $document = new Document($data); - $dbForProject->createDocument($collectionId, $document); - break; - case 'update': - $document = new Document($data); - $dbForProject->updateDocument($collectionId, $documentId, $document); - break; - - case 'upsert': - $document = new Document($data); - $dbForProject->createOrUpdateDocuments($collectionId, [$document]); - break; - - case 'delete': - $dbForProject->deleteDocument($collectionId, $documentId); + // Update the state document directly + $existing = $state[$collectionId][$documentId]; + foreach ($data as $key => $value) { + $existing->setAttribute($key, $value); + } + $state[$collectionId][$documentId] = $dbForProject->updateDocument( + $collectionId, + $documentId, + $existing + ); break; case 'increment': - $dbForProject->increaseDocumentAttribute( + $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, attribute: $data['attribute'], @@ -145,7 +143,7 @@ class Update extends Action break; case 'decrement': - $dbForProject->decreaseDocumentAttribute( + $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, attribute: $data['attribute'], @@ -153,51 +151,126 @@ class Update extends Action min: $data['min'] ?? null ); break; - - case 'bulkCreate': - $documents = []; - foreach ($data as $docData) { - $documents[] = new Document($docData); - } - $dbForProject->createDocuments($collectionId, $documents); - break; - - case 'bulkUpdate': - $dbForProject->updateDocuments( - $collectionId, - $data['data'] ?? null, - Query::parseQueries($data['queries'] ?? []) - ); - break; - - case 'bulkUpsert': - $documents = []; - foreach ($data as $docData) { - $documents[] = new Document($docData); - } - $dbForProject->createOrUpdateDocuments($collectionId, $documents); - break; - - case 'bulkDelete': - $dbForProject->deleteDocuments( - $collectionId, - Query::parseQueries($data['queries'] ?? []) - ); - break; } - }); + } else { + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $queueForDeletes, $action, $collectionId, $documentId, $data, &$state) { + switch ($action) { + case 'create': + if ($documentId && !isset($data['$id'])) { + $data['$id'] = $documentId; + } + $state[$collectionId][$documentId] = $dbForProject->createDocument( + $collectionId, + new Document($data), + ); + break; + + case 'update': + $document = new Document($data); + $state[$collectionId][$documentId] = $dbForProject->updateDocument( + $collectionId, + $documentId, + $document, + ); + break; + + case 'upsert': + $document = new Document($data); + $dbForProject->createOrUpdateDocuments( + $collectionId, + [$document], + onNext: function (Document $document) use (&$state, $collectionId) { + $state[$collectionId][$document->getId()] = $document; + } + ); + break; + + case 'delete': + $dbForProject->deleteDocument($collectionId, $documentId); + + if (isset($state[$collectionId][$documentId])) { + unset($state[$collectionId][$documentId]); + } + break; + + case 'increment': + $dbForProject->increaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + max: $data['max'] ?? null + ); + break; + + case 'decrement': + $dbForProject->decreaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + min: $data['min'] ?? null + ); + break; + + case 'bulkCreate': + $dbForProject->createDocuments( + $collectionId, + array_map(fn ($data) => new Document($data), $data), + onNext: function (Document $document) use (&$state, $collectionId) { + $state[$collectionId][$document->getId()] = $document; + + } + ); + break; + + case 'bulkUpdate': + $dbForProject->updateDocuments( + $collectionId, + $data['data'] ?? null, + Query::parseQueries($data['queries'] ?? []) + ); + break; + + case 'bulkUpsert': + $dbForProject->createOrUpdateDocuments( + $collectionId, + array_map(fn ($data) => new Document($data), $data), + onNext: function (Document $document) use (&$state, $collectionId) { + $state[$collectionId][$document->getId()] = $document; + + } + ); + break; + + case 'bulkDelete': + $dbForProject->deleteDocuments( + $collectionId, + Query::parseQueries($data['queries'] ?? []) + ); + break; + } + }); + } } - $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'committed', - ])); + $transaction = $dbForProject->updateDocument( + 'transactions', + $transactionId, + new Document( + [ + 'status' => 'committed', + ] + ) + ); // Clear the transaction logs $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); - } catch (DuplicateException|ConflictException) { + } catch (DuplicateException|ConflictException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php index 3b02ffaec0..b41d6adcdb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php @@ -5,9 +5,9 @@ namespace Appwrite\Platform\Modules\Databases\Services\Registry; use Appwrite\Platform\Modules\Databases\Http\Transactions\Create as CreateTransaction; use Appwrite\Platform\Modules\Databases\Http\Transactions\Delete as DeleteTransaction; use Appwrite\Platform\Modules\Databases\Http\Transactions\Get as GetTransaction; +use Appwrite\Platform\Modules\Databases\Http\Transactions\Operations\Create as CreateOperations; use Appwrite\Platform\Modules\Databases\Http\Transactions\Update as UpdateTransaction; use Appwrite\Platform\Modules\Databases\Http\Transactions\XList as ListTransactions; -use Appwrite\Platform\Modules\Databases\Http\Transactions\Operations\Create as CreateOperations; use Utopia\Platform\Service; /** @@ -24,4 +24,4 @@ class Transactions extends Base $service->addAction(ListTransactions::getName(), new ListTransactions()); $service->addAction(CreateOperations::getName(), new CreateOperations()); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index b68c758169..b647b57908 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -86,8 +86,7 @@ class Deletes extends Action string $executionRetention, string $auditRetention, Log $log - ): void - { + ): void { $payload = $message->getPayload() ?? []; if (empty($payload)) { @@ -611,7 +610,7 @@ class Deletes extends Action ); } elseif ($sharedTablesV2) { $queries = \array_map( - fn($id) => Query::notEqual('$id', $id), + fn ($id) => Query::notEqual('$id', $id), $projectCollectionIds ); @@ -955,14 +954,14 @@ class Deletes extends Action } Console::info("Deleting screenshots for deployment " . $deployment->getId()); - $bucket = ValidatorAuthorization::skip(fn() => $dbForPlatform->getDocument('buckets', 'screenshots')); + $bucket = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')); if ($bucket->isEmpty()) { Console::error('Failed to get bucket for deployment screenshots'); return; } foreach ($screenshotIds as $id) { - $file = ValidatorAuthorization::skip(fn() => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $id)); + $file = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $id)); if ($file->isEmpty()) { Console::error('Failed to get deployment screenshot: ' . $id); @@ -1119,8 +1118,7 @@ class Deletes extends Action array $queries, Database $database, ?callable $callback = null - ): void - { + ): void { $start = \microtime(true); /** diff --git a/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php b/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php index 4c0b0eecc3..42a2967685 100644 --- a/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php +++ b/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php @@ -7,12 +7,10 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; use Utopia\Database\Database; -use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Database\Query; class ACIDComplianceTest extends Scope { @@ -162,7 +160,7 @@ class ACIDComplianceTest extends Scope $this->assertGreaterThanOrEqual(1, $documents['body']['total']); } else { $this->assertEquals(409, $response['headers']['status-code']); // Conflict error - + // Verify NO new documents were created (atomicity) $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ 'content-type' => 'application/json', @@ -577,7 +575,7 @@ class ACIDComplianceTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertGreaterThan(0, $allDocs['body']['total'], 'Should have created documents. Found: ' . json_encode($allDocs['body'])); // Verify documents exist and have correct data @@ -625,4 +623,4 @@ class ACIDComplianceTest extends Scope $this->assertEquals(2, $documents['body']['total']); } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php index 35340f8b0e..1ed8e6963b 100644 --- a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php @@ -37,7 +37,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); $this->assertEquals(201, $response['headers']['status-code']); $this->assertArrayHasKey('$id', $response['body']); @@ -53,13 +54,14 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'ttl' => 900 ]); $this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals('pending', $response['body']['status']); - + $expiresAt = new \DateTime($response['body']['expiresAt']); $now = new \DateTime(); $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); @@ -72,7 +74,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'ttl' => 30 // Below minimum ]); @@ -81,7 +84,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'ttl' => 4000 // Above maximum ]); @@ -142,7 +146,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'operations' => [ [ 'databaseId' => $databaseId, @@ -174,7 +179,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'operations' => [ [ 'databaseId' => $databaseId, @@ -195,7 +201,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'operations' => [ [ 'databaseId' => 'invalid_database', @@ -212,7 +219,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'operations' => [ [ 'databaseId' => $databaseId, @@ -243,7 +251,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'commit' => true ]); @@ -258,7 +267,7 @@ class DatabasesTransactionsTest extends Scope $this->assertEquals(200, $documents['headers']['status-code']); $this->assertEquals(2, $documents['body']['total']); - + // Verify the update was applied $doc1Found = false; foreach ($documents['body']['documents'] as $doc) { @@ -273,7 +282,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'commit' => true ]); @@ -324,7 +334,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'operations' => [ [ 'databaseId' => $databaseId, @@ -344,7 +355,8 @@ class DatabasesTransactionsTest extends Scope $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'rollback' => true ]); @@ -360,4 +372,4 @@ class DatabasesTransactionsTest extends Scope $this->assertEquals(200, $documents['headers']['status-code']); $this->assertEquals(0, $documents['body']['total']); } -} \ No newline at end of file +} From 1d8d757960ed8a252d40e1dd6b83d6bfc137d755 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 04:32:59 +1200 Subject: [PATCH 103/385] Add txn to workflow --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cebdc02163..75fb2cce26 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -169,6 +169,7 @@ jobs: Console, Databases/Legacy, Databases/TablesDB, + Databases/Transactions, Functions, FunctionsSchedule, GraphQL, From 76287beca038f58c2bbdd79a8a9c5127ce674055 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 04:36:34 +1200 Subject: [PATCH 104/385] Format --- app/config/collections/projects.php | 170 ++++++++++++++-------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index e48dcef451..7bd1d4a7d8 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2524,138 +2524,138 @@ return [ 'transactions' => [ '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('transactions'), - 'name' => 'Transactions', - 'attributes' => [ + '$id' => ID::custom('transactions'), + 'name' => 'Transactions', + 'attributes' => [ [ - '$id' => ID::custom('status'), - 'type' => Database::VAR_STRING, - 'size' => 16, // pending | committing | committed | rolled_back | failed - 'signed' => true, + '$id' => ID::custom('status'), + 'type' => Database::VAR_STRING, + 'size' => 16, // pending | committing | committed | rolled_back | failed + 'signed' => true, 'required' => false, - 'default' => 'pending', - 'array' => false, - 'filters' => [], + 'default' => 'pending', + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('operations'), - 'type' => Database::VAR_INTEGER, - 'size' => 0, - 'signed' => false, + '$id' => ID::custom('operations'), + 'type' => Database::VAR_INTEGER, + 'size' => 0, + 'signed' => false, 'required' => true, - 'default' => 0, - 'array' => false, - 'filters' => [], + 'default' => 0, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('expiresAt'), - 'type' => Database::VAR_DATETIME, - 'size' => 0, - 'signed' => true, + '$id' => ID::custom('expiresAt'), + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'signed' => true, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], ], ], 'indexes' => [ [ - '$id' => ID::custom('_key_status'), - 'type' => Database::INDEX_KEY, + '$id' => ID::custom('_key_status'), + 'type' => Database::INDEX_KEY, 'attributes' => ['status'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_expires'), - 'type' => Database::INDEX_KEY, + '$id' => ID::custom('_key_expires'), + 'type' => Database::INDEX_KEY, 'attributes' => ['expiresAt'], - 'lengths' => [], - 'orders' => [Database::ORDER_DESC], + 'lengths' => [], + 'orders' => [Database::ORDER_DESC], ], ], ], 'transactionLogs' => [ '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('transactionLogs'), - 'name' => 'Transaction Logs', - 'attributes' => [ + '$id' => ID::custom('transactionLogs'), + 'name' => 'Transaction Logs', + 'attributes' => [ [ - '$id' => ID::custom('transactionInternalId'), - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'signed' => true, + '$id' => ID::custom('transactionInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], + 'default' => null, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('databaseInternalId'), - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'signed' => true, + '$id' => ID::custom('databaseInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], + 'default' => null, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('collectionInternalId'), - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'signed' => true, + '$id' => ID::custom('collectionInternalId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], + 'default' => null, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('documentId'), - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'signed' => true, + '$id' => ID::custom('documentId'), + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], + 'default' => null, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('action'), - 'type' => Database::VAR_STRING, - 'size' => 32, // create | update | upsert | increment | decrement | delete - 'signed' => true, + '$id' => ID::custom('action'), + 'type' => Database::VAR_STRING, + 'size' => 32, // create | update | upsert | increment | decrement | delete + 'signed' => true, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], + 'default' => null, + 'array' => false, + 'filters' => [], ], [ - '$id' => ID::custom('data'), - 'type' => Database::VAR_STRING, - 'size' => 65535, - 'signed' => false, + '$id' => ID::custom('data'), + 'type' => Database::VAR_STRING, + 'size' => 65535, + 'signed' => false, 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => ['json'], + 'default' => null, + 'array' => false, + 'filters' => ['json'], ], ], 'indexes' => [ [ - '$id' => ID::custom('_key_transaction'), - 'type' => Database::INDEX_KEY, + '$id' => ID::custom('_key_transaction'), + 'type' => Database::INDEX_KEY, 'attributes' => ['transactionInternalId'], - 'lengths' => [], - 'orders' => [], + 'lengths' => [], + 'orders' => [], ], [ - '$id' => ID::custom('_key_internal_path'), - 'type' => Database::INDEX_KEY, + '$id' => ID::custom('_key_internal_path'), + 'type' => Database::INDEX_KEY, 'attributes' => ['databaseInternalId', 'collectionInternalId'], - 'lengths' => [], - 'orders' => [], + 'lengths' => [], + 'orders' => [], ], ], ], From c34efeff1ebc30d1255db93b7feda2ac987f6f30 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 04:37:48 +1200 Subject: [PATCH 105/385] Add to the other workflow --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 75fb2cce26..2ca0c58db8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -249,6 +249,7 @@ jobs: Console, Databases/Legacy, Databases/TablesDB, + Databases/Transactions, Functions, FunctionsSchedule, GraphQL, From 00f91c444c47b6b30acc7f04cba44ae01880380f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 04:55:31 +1200 Subject: [PATCH 106/385] Fix tests --- .../Databases/Transactions/DatabasesTransactionsTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php index 1ed8e6963b..513051fcc0 100644 --- a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php @@ -155,7 +155,6 @@ class DatabasesTransactionsTest extends Scope 'action' => 'create', 'documentId' => 'doc1', 'data' => [ - '$id' => 'doc1', 'name' => 'Test Document 1' ] ], @@ -165,7 +164,6 @@ class DatabasesTransactionsTest extends Scope 'action' => 'create', 'documentId' => 'doc2', 'data' => [ - '$id' => 'doc2', 'name' => 'Test Document 2' ] ] @@ -208,12 +206,13 @@ class DatabasesTransactionsTest extends Scope 'databaseId' => 'invalid_database', 'collectionId' => $collectionId, 'action' => 'create', + 'documentId' => ID::unique(), 'data' => ['name' => 'Test'] ] ] ]); - $this->assertEquals(404, $response['headers']['status-code']); + $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); // Test invalid collection ID $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ @@ -226,6 +225,7 @@ class DatabasesTransactionsTest extends Scope 'databaseId' => $databaseId, 'collectionId' => 'invalid_collection', 'action' => 'create', + 'documentId' => ID::unique(), 'data' => ['name' => 'Test'] ] ] @@ -341,8 +341,8 @@ class DatabasesTransactionsTest extends Scope 'databaseId' => $databaseId, 'collectionId' => $collectionId, 'action' => 'create', + 'documentId' => 'rollback_doc', 'data' => [ - '$id' => 'rollback_doc', 'value' => 'Should not exist' ] ] From 903e9b52c23ba7ee2b97227eae889d5245fe6b74 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 3 Sep 2025 23:54:21 +1200 Subject: [PATCH 107/385] Fix size limit exception --- app/config/errors.php | 4 +- app/controllers/general.php | 3 +- app/init/constants.php | 1 + src/Appwrite/Extend/Exception.php | 502 +++++++++--------- .../Databases/Http/Transactions/Update.php | 6 + 5 files changed, 265 insertions(+), 251 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index fa45550ba9..098c87c574 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -988,8 +988,8 @@ return [ ], Exception::TRANSACTION_LIMIT_EXCEEDED => [ 'name' => Exception::TRANSACTION_LIMIT_EXCEEDED, - 'description' => 'The maximum number of transactions has been reached.', - 'code' => 400, + 'description' => 'The maximum number of operations per transaction has been exceeded.', + 'code' => 422, ], Exception::TRANSACTION_NOT_READY => [ 'name' => Exception::TRANSACTION_NOT_READY, diff --git a/app/controllers/general.php b/app/controllers/general.php index 40ce66b574..a87aa45c4f 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1231,7 +1231,7 @@ App::error() } /** - * If its not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php + * If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php */ if (!$publish && $project->getId() !== 'console') { if (!Auth::isPrivilegedUser(Authorization::getRoles())) { @@ -1333,6 +1333,7 @@ App::error() case 409: // Error allowed publicly case 412: // Error allowed publicly case 416: // Error allowed publicly + case 422: // Error allowed publicly case 429: // Error allowed publicly case 451: // Error allowed publicly case 501: // Error allowed publicly diff --git a/app/init/constants.php b/app/init/constants.php index 2e22a4ca3c..2a068eba97 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -56,6 +56,7 @@ const APP_DATABASE_ENCRYPT_SIZE_MIN = 150; const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour const APP_DATABASE_TXN_TTL_DEFAULT = 300; // 5 minutes +const APP_DATABASE_TXN_MAX_OPERATIONS = 100; // Maximum operations per transaction const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_SITES = '/storage/sites'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 558f402ea2..cfa55c540e 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -36,341 +36,341 @@ class Exception extends \Exception */ /** General */ - public const GENERAL_UNKNOWN = 'general_unknown'; - public const GENERAL_MOCK = 'general_mock'; - public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden'; - public const GENERAL_RESOURCE_BLOCKED = 'general_resource_blocked'; - public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin'; - public const GENERAL_API_DISABLED = 'general_api_disabled'; - public const GENERAL_SERVICE_DISABLED = 'general_service_disabled'; - public const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope'; - public const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded'; - public const GENERAL_SMTP_DISABLED = 'general_smtp_disabled'; - public const GENERAL_PHONE_DISABLED = 'general_phone_disabled'; - public const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid'; - public const GENERAL_COLUMN_QUERY_LIMIT_EXCEEDED = 'general_column_query_limit_exceeded'; - public const GENERAL_ATTRIBUTE_QUERY_LIMIT_EXCEEDED = 'general_attribute_query_limit_exceeded'; - public const GENERAL_QUERY_INVALID = 'general_query_invalid'; - public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found'; - public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found'; - public const GENERAL_SERVER_ERROR = 'general_server_error'; - public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported'; - public const GENERAL_CODES_DISABLED = 'general_codes_disabled'; - public const GENERAL_USAGE_DISABLED = 'general_usage_disabled'; - public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented'; - public const GENERAL_INVALID_EMAIL = 'general_invalid_email'; - public const GENERAL_INVALID_PHONE = 'general_invalid_phone'; - public const GENERAL_REGION_ACCESS_DENIED = 'general_region_access_denied'; - public const GENERAL_BAD_REQUEST = 'general_bad_request'; + public const string GENERAL_UNKNOWN = 'general_unknown'; + public const string GENERAL_MOCK = 'general_mock'; + public const string GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden'; + public const string GENERAL_RESOURCE_BLOCKED = 'general_resource_blocked'; + public const string GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin'; + public const string GENERAL_API_DISABLED = 'general_api_disabled'; + public const string GENERAL_SERVICE_DISABLED = 'general_service_disabled'; + public const string GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope'; + public const string GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded'; + public const string GENERAL_SMTP_DISABLED = 'general_smtp_disabled'; + public const string GENERAL_PHONE_DISABLED = 'general_phone_disabled'; + public const string GENERAL_ARGUMENT_INVALID = 'general_argument_invalid'; + public const string GENERAL_COLUMN_QUERY_LIMIT_EXCEEDED = 'general_column_query_limit_exceeded'; + public const string GENERAL_ATTRIBUTE_QUERY_LIMIT_EXCEEDED = 'general_attribute_query_limit_exceeded'; + public const string GENERAL_QUERY_INVALID = 'general_query_invalid'; + public const string GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found'; + public const string GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found'; + public const string GENERAL_SERVER_ERROR = 'general_server_error'; + public const string GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported'; + public const string GENERAL_CODES_DISABLED = 'general_codes_disabled'; + public const string GENERAL_USAGE_DISABLED = 'general_usage_disabled'; + public const string GENERAL_NOT_IMPLEMENTED = 'general_not_implemented'; + public const string GENERAL_INVALID_EMAIL = 'general_invalid_email'; + public const string GENERAL_INVALID_PHONE = 'general_invalid_phone'; + public const string GENERAL_REGION_ACCESS_DENIED = 'general_region_access_denied'; + public const string GENERAL_BAD_REQUEST = 'general_bad_request'; /** Users */ - public const USER_COUNT_EXCEEDED = 'user_count_exceeded'; - public const USER_CONSOLE_COUNT_EXCEEDED = 'user_console_count_exceeded'; - public const USER_JWT_INVALID = 'user_jwt_invalid'; - public const USER_ALREADY_EXISTS = 'user_already_exists'; - public const USER_BLOCKED = 'user_blocked'; - public const USER_INVALID_TOKEN = 'user_invalid_token'; - public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required'; - public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted'; - public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted'; - public const USER_INVALID_CODE = 'user_invalid_code'; - public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials'; - public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited'; - public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists'; - public const USER_NOT_FOUND = 'user_not_found'; - public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used'; - public const USER_PASSWORD_PERSONAL_DATA = 'password_personal_data'; - public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists'; - public const USER_PASSWORD_MISMATCH = 'user_password_mismatch'; - public const USER_SESSION_NOT_FOUND = 'user_session_not_found'; - public const USER_IDENTITY_NOT_FOUND = 'user_identity_not_found'; - public const USER_UNAUTHORIZED = 'user_unauthorized'; - public const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported'; - public const USER_PHONE_ALREADY_EXISTS = 'user_phone_already_exists'; - public const USER_PHONE_NOT_FOUND = 'user_phone_not_found'; - public const USER_PHONE_NOT_VERIFIED = 'user_phone_not_verified'; - public const USER_EMAIL_NOT_FOUND = 'user_email_not_found'; - public const USER_EMAIL_NOT_VERIFIED = 'user_email_not_verified'; - public const USER_MISSING_ID = 'user_missing_id'; - public const USER_MORE_FACTORS_REQUIRED = 'user_more_factors_required'; - public const USER_INVALID_CHALLENGE = 'user_invalid_challenge'; - public const USER_AUTHENTICATOR_NOT_FOUND = 'user_authenticator_not_found'; - public const USER_AUTHENTICATOR_ALREADY_VERIFIED = 'user_authenticator_already_verified'; - public const USER_RECOVERY_CODES_ALREADY_EXISTS = 'user_recovery_codes_already_exists'; - public const USER_RECOVERY_CODES_NOT_FOUND = 'user_recovery_codes_not_found'; - public const USER_CHALLENGE_REQUIRED = 'user_challenge_required'; - public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request'; - public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; - public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error'; - public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_already_verified'; - public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; - public const USER_DELETION_PROHIBITED = 'user_deletion_prohibited'; - public const USER_TARGET_NOT_FOUND = 'user_target_not_found'; - public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists'; - public const USER_API_KEY_AND_SESSION_SET = 'user_key_and_session_set'; + public const string USER_COUNT_EXCEEDED = 'user_count_exceeded'; + public const string USER_CONSOLE_COUNT_EXCEEDED = 'user_console_count_exceeded'; + public const string USER_JWT_INVALID = 'user_jwt_invalid'; + public const string USER_ALREADY_EXISTS = 'user_already_exists'; + public const string USER_BLOCKED = 'user_blocked'; + public const string USER_INVALID_TOKEN = 'user_invalid_token'; + public const string USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required'; + public const string USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted'; + public const string USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted'; + public const string USER_INVALID_CODE = 'user_invalid_code'; + public const string USER_INVALID_CREDENTIALS = 'user_invalid_credentials'; + public const string USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited'; + public const string USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists'; + public const string USER_NOT_FOUND = 'user_not_found'; + public const string USER_PASSWORD_RECENTLY_USED = 'password_recently_used'; + public const string USER_PASSWORD_PERSONAL_DATA = 'password_personal_data'; + public const string USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists'; + public const string USER_PASSWORD_MISMATCH = 'user_password_mismatch'; + public const string USER_SESSION_NOT_FOUND = 'user_session_not_found'; + public const string USER_IDENTITY_NOT_FOUND = 'user_identity_not_found'; + public const string USER_UNAUTHORIZED = 'user_unauthorized'; + public const string USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported'; + public const string USER_PHONE_ALREADY_EXISTS = 'user_phone_already_exists'; + public const string USER_PHONE_NOT_FOUND = 'user_phone_not_found'; + public const string USER_PHONE_NOT_VERIFIED = 'user_phone_not_verified'; + public const string USER_EMAIL_NOT_FOUND = 'user_email_not_found'; + public const string USER_EMAIL_NOT_VERIFIED = 'user_email_not_verified'; + public const string USER_MISSING_ID = 'user_missing_id'; + public const string USER_MORE_FACTORS_REQUIRED = 'user_more_factors_required'; + public const string USER_INVALID_CHALLENGE = 'user_invalid_challenge'; + public const string USER_AUTHENTICATOR_NOT_FOUND = 'user_authenticator_not_found'; + public const string USER_AUTHENTICATOR_ALREADY_VERIFIED = 'user_authenticator_already_verified'; + public const string USER_RECOVERY_CODES_ALREADY_EXISTS = 'user_recovery_codes_already_exists'; + public const string USER_RECOVERY_CODES_NOT_FOUND = 'user_recovery_codes_not_found'; + public const string USER_CHALLENGE_REQUIRED = 'user_challenge_required'; + public const string USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request'; + public const string USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; + public const string USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error'; + public const string USER_EMAIL_ALREADY_VERIFIED = 'user_email_already_verified'; + public const string USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; + public const string USER_DELETION_PROHIBITED = 'user_deletion_prohibited'; + public const string USER_TARGET_NOT_FOUND = 'user_target_not_found'; + public const string USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists'; + public const string USER_API_KEY_AND_SESSION_SET = 'user_key_and_session_set'; - public const API_KEY_EXPIRED = 'api_key_expired'; + public const string API_KEY_EXPIRED = 'api_key_expired'; /** Teams */ - public const TEAM_NOT_FOUND = 'team_not_found'; - public const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found'; - public const TEAM_INVALID_SECRET = 'team_invalid_secret'; - public const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch'; - public const TEAM_INVITE_MISMATCH = 'team_invite_mismatch'; - public const TEAM_ALREADY_EXISTS = 'team_already_exists'; + public const string TEAM_NOT_FOUND = 'team_not_found'; + public const string TEAM_INVITE_NOT_FOUND = 'team_invite_not_found'; + public const string TEAM_INVALID_SECRET = 'team_invalid_secret'; + public const string TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch'; + public const string TEAM_INVITE_MISMATCH = 'team_invite_mismatch'; + public const string TEAM_ALREADY_EXISTS = 'team_already_exists'; /** Console */ - public const RESOURCE_ALREADY_EXISTS = 'resource_already_exists'; + public const string RESOURCE_ALREADY_EXISTS = 'resource_already_exists'; /** Membership */ - public const MEMBERSHIP_NOT_FOUND = 'membership_not_found'; - public const MEMBERSHIP_ALREADY_CONFIRMED = 'membership_already_confirmed'; - public const MEMBERSHIP_DELETION_PROHIBITED = 'membership_deletion_prohibited'; - public const MEMBERSHIP_DOWNGRADE_PROHIBITED = 'membership_downgrade_prohibited'; + public const string MEMBERSHIP_NOT_FOUND = 'membership_not_found'; + public const string MEMBERSHIP_ALREADY_CONFIRMED = 'membership_already_confirmed'; + public const string MEMBERSHIP_DELETION_PROHIBITED = 'membership_deletion_prohibited'; + public const string MEMBERSHIP_DOWNGRADE_PROHIBITED = 'membership_downgrade_prohibited'; /** Avatars */ - public const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found'; - public const AVATAR_NOT_FOUND = 'avatar_not_found'; - public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found'; - public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed'; - public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found'; - public const AVATAR_SVG_SANITIZATION_FAILED = 'avatar_svg_sanitization_failed'; + public const string AVATAR_SET_NOT_FOUND = 'avatar_set_not_found'; + public const string AVATAR_NOT_FOUND = 'avatar_not_found'; + public const string AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found'; + public const string AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed'; + public const string AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found'; + public const string AVATAR_SVG_SANITIZATION_FAILED = 'avatar_svg_sanitization_failed'; /** Storage */ - public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists'; - public const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found'; - public const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found'; - public const STORAGE_FILE_EMPTY = 'storage_file_empty'; - public const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported'; - public const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size'; - public const STORAGE_INVALID_FILE = 'storage_invalid_file'; - public const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists'; - public const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found'; - public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range'; - public const STORAGE_INVALID_RANGE = 'storage_invalid_range'; - public const STORAGE_INVALID_APPWRITE_ID = 'storage_invalid_appwrite_id'; - public const STORAGE_FILE_NOT_PUBLIC = 'storage_file_not_public'; + public const string STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists'; + public const string STORAGE_FILE_NOT_FOUND = 'storage_file_not_found'; + public const string STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found'; + public const string STORAGE_FILE_EMPTY = 'storage_file_empty'; + public const string STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported'; + public const string STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size'; + public const string STORAGE_INVALID_FILE = 'storage_invalid_file'; + public const string STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists'; + public const string STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found'; + public const string STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range'; + public const string STORAGE_INVALID_RANGE = 'storage_invalid_range'; + public const string STORAGE_INVALID_APPWRITE_ID = 'storage_invalid_appwrite_id'; + public const string STORAGE_FILE_NOT_PUBLIC = 'storage_file_not_public'; /** VCS */ - public const INSTALLATION_NOT_FOUND = 'installation_not_found'; - public const PROVIDER_REPOSITORY_NOT_FOUND = 'provider_repository_not_found'; - public const REPOSITORY_NOT_FOUND = 'repository_not_found'; - public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict'; - public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure'; + public const string INSTALLATION_NOT_FOUND = 'installation_not_found'; + public const string PROVIDER_REPOSITORY_NOT_FOUND = 'provider_repository_not_found'; + public const string REPOSITORY_NOT_FOUND = 'repository_not_found'; + public const string PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict'; + public const string GENERAL_PROVIDER_FAILURE = 'general_provider_failure'; /** Sites */ - public const SITE_NOT_FOUND = 'site_not_found'; - public const SITE_TEMPLATE_NOT_FOUND = 'site_template_not_found'; + public const string SITE_NOT_FOUND = 'site_not_found'; + public const string SITE_TEMPLATE_NOT_FOUND = 'site_template_not_found'; /** Functions */ - public const FUNCTION_NOT_FOUND = 'function_not_found'; - public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; - public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; - public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout'; - public const FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found'; - public const FUNCTION_RUNTIME_NOT_DETECTED = 'function_runtime_not_detected'; - public const FUNCTION_EXECUTE_PERMISSION_MISSING = 'function_execute_permission_missing'; + public const string FUNCTION_NOT_FOUND = 'function_not_found'; + public const string FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; + public const string FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; + public const string FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout'; + public const string FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found'; + public const string FUNCTION_RUNTIME_NOT_DETECTED = 'function_runtime_not_detected'; + public const string FUNCTION_EXECUTE_PERMISSION_MISSING = 'function_execute_permission_missing'; /** Deployments */ - public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; + public const string DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; /** Builds */ - public const BUILD_NOT_FOUND = 'build_not_found'; - public const BUILD_NOT_READY = 'build_not_ready'; - public const BUILD_IN_PROGRESS = 'build_in_progress'; - public const BUILD_ALREADY_COMPLETED = 'build_already_completed'; - public const BUILD_CANCELED = 'build_canceled'; - public const BUILD_FAILED = 'build_failed'; + public const string BUILD_NOT_FOUND = 'build_not_found'; + public const string BUILD_NOT_READY = 'build_not_ready'; + public const string BUILD_IN_PROGRESS = 'build_in_progress'; + public const string BUILD_ALREADY_COMPLETED = 'build_already_completed'; + public const string BUILD_CANCELED = 'build_canceled'; + public const string BUILD_FAILED = 'build_failed'; /** Execution */ - public const EXECUTION_NOT_FOUND = 'execution_not_found'; - public const EXECUTION_IN_PROGRESS = 'execution_in_progress'; + public const string EXECUTION_NOT_FOUND = 'execution_not_found'; + public const string EXECUTION_IN_PROGRESS = 'execution_in_progress'; /** Log */ - public const LOG_NOT_FOUND = 'log_not_found'; + public const string LOG_NOT_FOUND = 'log_not_found'; /** Databases */ - public const DATABASE_NOT_FOUND = 'database_not_found'; - public const DATABASE_ALREADY_EXISTS = 'database_already_exists'; - public const DATABASE_TIMEOUT = 'database_timeout'; - public const DATABASE_QUERY_ORDER_NULL = 'database_query_order_null'; + public const string DATABASE_NOT_FOUND = 'database_not_found'; + public const string DATABASE_ALREADY_EXISTS = 'database_already_exists'; + public const string DATABASE_TIMEOUT = 'database_timeout'; + public const string DATABASE_QUERY_ORDER_NULL = 'database_query_order_null'; /** Collections */ - public const COLLECTION_NOT_FOUND = 'collection_not_found'; - public const COLLECTION_ALREADY_EXISTS = 'collection_already_exists'; - public const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded'; + public const string COLLECTION_NOT_FOUND = 'collection_not_found'; + public const string COLLECTION_ALREADY_EXISTS = 'collection_already_exists'; + public const string COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded'; /** Tables */ - public const TABLE_NOT_FOUND = 'table_not_found'; - public const TABLE_ALREADY_EXISTS = 'table_already_exists'; - public const TABLE_LIMIT_EXCEEDED = 'table_limit_exceeded'; + public const string TABLE_NOT_FOUND = 'table_not_found'; + public const string TABLE_ALREADY_EXISTS = 'table_already_exists'; + public const string TABLE_LIMIT_EXCEEDED = 'table_limit_exceeded'; /** Documents */ - public const DOCUMENT_NOT_FOUND = 'document_not_found'; - public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure'; - public const DOCUMENT_MISSING_DATA = 'document_missing_data'; - public const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload'; - public const DOCUMENT_ALREADY_EXISTS = 'document_already_exists'; - public const DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict'; - public const DOCUMENT_DELETE_RESTRICTED = 'document_delete_restricted'; + public const string DOCUMENT_NOT_FOUND = 'document_not_found'; + public const string DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure'; + public const string DOCUMENT_MISSING_DATA = 'document_missing_data'; + public const string DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload'; + public const string DOCUMENT_ALREADY_EXISTS = 'document_already_exists'; + public const string DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict'; + public const string DOCUMENT_DELETE_RESTRICTED = 'document_delete_restricted'; /** Rows */ - public const ROW_NOT_FOUND = 'row_not_found'; - public const ROW_INVALID_STRUCTURE = 'row_invalid_structure'; - public const ROW_MISSING_DATA = 'row_missing_data'; - public const ROW_MISSING_PAYLOAD = 'row_missing_payload'; - public const ROW_ALREADY_EXISTS = 'row_already_exists'; - public const ROW_UPDATE_CONFLICT = 'row_update_conflict'; - public const ROW_DELETE_RESTRICTED = 'row_delete_restricted'; + public const string ROW_NOT_FOUND = 'row_not_found'; + public const string ROW_INVALID_STRUCTURE = 'row_invalid_structure'; + public const string ROW_MISSING_DATA = 'row_missing_data'; + public const string ROW_MISSING_PAYLOAD = 'row_missing_payload'; + public const string ROW_ALREADY_EXISTS = 'row_already_exists'; + public const string ROW_UPDATE_CONFLICT = 'row_update_conflict'; + public const string ROW_DELETE_RESTRICTED = 'row_delete_restricted'; /** Attributes */ - public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found'; - public const ATTRIBUTE_UNKNOWN = 'attribute_unknown'; - public const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available'; - public const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported'; - public const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported'; - public const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists'; - public const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded'; - public const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid'; - public const ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid'; - public const ATTRIBUTE_INVALID_RESIZE = 'attribute_invalid_resize'; + public const string ATTRIBUTE_NOT_FOUND = 'attribute_not_found'; + public const string ATTRIBUTE_UNKNOWN = 'attribute_unknown'; + public const string ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available'; + public const string ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported'; + public const string ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported'; + public const string ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists'; + public const string ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded'; + public const string ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid'; + public const string ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid'; + public const string ATTRIBUTE_INVALID_RESIZE = 'attribute_invalid_resize'; /** Columns */ - public const COLUMN_NOT_FOUND = 'column_not_found'; - public const COLUMN_UNKNOWN = 'column_unknown'; - public const COLUMN_NOT_AVAILABLE = 'column_not_available'; - public const COLUMN_FORMAT_UNSUPPORTED = 'column_format_unsupported'; - public const COLUMN_DEFAULT_UNSUPPORTED = 'column_default_unsupported'; - public const COLUMN_ALREADY_EXISTS = 'column_already_exists'; - public const COLUMN_LIMIT_EXCEEDED = 'column_limit_exceeded'; - public const COLUMN_VALUE_INVALID = 'column_value_invalid'; - public const COLUMN_TYPE_INVALID = 'column_type_invalid'; - public const COLUMN_INVALID_RESIZE = 'column_invalid_resize'; + public const string COLUMN_NOT_FOUND = 'column_not_found'; + public const string COLUMN_UNKNOWN = 'column_unknown'; + public const string COLUMN_NOT_AVAILABLE = 'column_not_available'; + public const string COLUMN_FORMAT_UNSUPPORTED = 'column_format_unsupported'; + public const string COLUMN_DEFAULT_UNSUPPORTED = 'column_default_unsupported'; + public const string COLUMN_ALREADY_EXISTS = 'column_already_exists'; + public const string COLUMN_LIMIT_EXCEEDED = 'column_limit_exceeded'; + public const string COLUMN_VALUE_INVALID = 'column_value_invalid'; + public const string COLUMN_TYPE_INVALID = 'column_type_invalid'; + public const string COLUMN_INVALID_RESIZE = 'column_invalid_resize'; /** Relationship */ - public const RELATIONSHIP_VALUE_INVALID = 'relationship_value_invalid'; + public const string RELATIONSHIP_VALUE_INVALID = 'relationship_value_invalid'; /** Indexes */ - public const INDEX_NOT_FOUND = 'index_not_found'; - public const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded'; - public const INDEX_ALREADY_EXISTS = 'index_already_exists'; - public const INDEX_INVALID = 'index_invalid'; - public const INDEX_DEPENDENCY = 'index_dependency'; + public const string INDEX_NOT_FOUND = 'index_not_found'; + public const string INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded'; + public const string INDEX_ALREADY_EXISTS = 'index_already_exists'; + public const string INDEX_INVALID = 'index_invalid'; + public const string INDEX_DEPENDENCY = 'index_dependency'; /** Column Indexes */ - public const COLUMN_INDEX_NOT_FOUND = 'column_index_not_found'; - public const COLUMN_INDEX_LIMIT_EXCEEDED = 'column_index_limit_exceeded'; - public const COLUMN_INDEX_ALREADY_EXISTS = 'column_index_already_exists'; - public const COLUMN_INDEX_INVALID = 'column_index_invalid'; - public const COLUMN_INDEX_DEPENDENCY = 'column_index_dependency'; + public const string COLUMN_INDEX_NOT_FOUND = 'column_index_not_found'; + public const string COLUMN_INDEX_LIMIT_EXCEEDED = 'column_index_limit_exceeded'; + public const string COLUMN_INDEX_ALREADY_EXISTS = 'column_index_already_exists'; + public const string COLUMN_INDEX_INVALID = 'column_index_invalid'; + public const string COLUMN_INDEX_DEPENDENCY = 'column_index_dependency'; /** Transactions */ - public const TRANSACTION_NOT_FOUND = 'transaction_not_found'; - public const TRANSACTION_ALREADY_EXISTS = 'transaction_already_exists'; - public const TRANSACTION_INVALID = 'transaction_invalid'; - public const TRANSACTION_FAILED = 'transaction_expired'; - public const TRANSACTION_EXPIRED = 'transaction_expired'; - public const TRANSACTION_CONFLICT = 'transaction_conflict'; - public const TRANSACTION_LIMIT_EXCEEDED = 'transaction_limit_exceeded'; - public const TRANSACTION_NOT_READY = 'transaction_not_ready'; + public const string TRANSACTION_NOT_FOUND = 'transaction_not_found'; + public const string TRANSACTION_ALREADY_EXISTS = 'transaction_already_exists'; + public const string TRANSACTION_INVALID = 'transaction_invalid'; + public const string TRANSACTION_FAILED = 'transaction_failed'; + public const string TRANSACTION_EXPIRED = 'transaction_expired'; + public const string TRANSACTION_CONFLICT = 'transaction_conflict'; + public const string TRANSACTION_LIMIT_EXCEEDED = 'transaction_limit_exceeded'; + public const string TRANSACTION_NOT_READY = 'transaction_not_ready'; /** Projects */ - public const PROJECT_NOT_FOUND = 'project_not_found'; - public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled'; - public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported'; - public const PROJECT_ALREADY_EXISTS = 'project_already_exists'; - public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url'; - public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url'; - public const PROJECT_RESERVED_PROJECT = 'project_reserved_project'; - public const PROJECT_KEY_EXPIRED = 'project_key_expired'; + public const string PROJECT_NOT_FOUND = 'project_not_found'; + public const string PROJECT_PROVIDER_DISABLED = 'project_provider_disabled'; + public const string PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported'; + public const string PROJECT_ALREADY_EXISTS = 'project_already_exists'; + public const string PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url'; + public const string PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url'; + public const string PROJECT_RESERVED_PROJECT = 'project_reserved_project'; + public const string PROJECT_KEY_EXPIRED = 'project_key_expired'; - public const PROJECT_SMTP_CONFIG_INVALID = 'project_smtp_config_invalid'; + public const string PROJECT_SMTP_CONFIG_INVALID = 'project_smtp_config_invalid'; - public const PROJECT_TEMPLATE_DEFAULT_DELETION = 'project_template_default_deletion'; + public const string PROJECT_TEMPLATE_DEFAULT_DELETION = 'project_template_default_deletion'; - public const PROJECT_REGION_UNSUPPORTED = 'project_region_unsupported'; + public const string PROJECT_REGION_UNSUPPORTED = 'project_region_unsupported'; /** Webhooks */ - public const WEBHOOK_NOT_FOUND = 'webhook_not_found'; + public const string WEBHOOK_NOT_FOUND = 'webhook_not_found'; /** Router */ - public const ROUTER_HOST_NOT_FOUND = 'router_host_not_found'; - public const ROUTER_DOMAIN_NOT_CONFIGURED = 'router_domain_not_configured'; + public const string ROUTER_HOST_NOT_FOUND = 'router_host_not_found'; + public const string ROUTER_DOMAIN_NOT_CONFIGURED = 'router_domain_not_configured'; /** Proxy */ - public const RULE_RESOURCE_NOT_FOUND = 'rule_resource_not_found'; - public const RULE_NOT_FOUND = 'rule_not_found'; - public const RULE_ALREADY_EXISTS = 'rule_already_exists'; - public const RULE_VERIFICATION_FAILED = 'rule_verification_failed'; + public const string RULE_RESOURCE_NOT_FOUND = 'rule_resource_not_found'; + public const string RULE_NOT_FOUND = 'rule_not_found'; + public const string RULE_ALREADY_EXISTS = 'rule_already_exists'; + public const string RULE_VERIFICATION_FAILED = 'rule_verification_failed'; /** Keys */ - public const KEY_NOT_FOUND = 'key_not_found'; + public const string KEY_NOT_FOUND = 'key_not_found'; /** Variables */ - public const VARIABLE_NOT_FOUND = 'variable_not_found'; - public const VARIABLE_ALREADY_EXISTS = 'variable_already_exists'; - public const VARIABLE_CANNOT_UNSET_SECRET = 'variable_cannot_unset_secret'; + public const string VARIABLE_NOT_FOUND = 'variable_not_found'; + public const string VARIABLE_ALREADY_EXISTS = 'variable_already_exists'; + public const string VARIABLE_CANNOT_UNSET_SECRET = 'variable_cannot_unset_secret'; /** Platform */ - public const PLATFORM_NOT_FOUND = 'platform_not_found'; + public const string PLATFORM_NOT_FOUND = 'platform_not_found'; /** GraphqQL */ - public const GRAPHQL_NO_QUERY = 'graphql_no_query'; - public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries'; + public const string GRAPHQL_NO_QUERY = 'graphql_no_query'; + public const string GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries'; /** Migrations */ - public const MIGRATION_NOT_FOUND = 'migration_not_found'; - public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists'; - public const MIGRATION_IN_PROGRESS = 'migration_in_progress'; - public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; + public const string MIGRATION_NOT_FOUND = 'migration_not_found'; + public const string MIGRATION_ALREADY_EXISTS = 'migration_already_exists'; + public const string MIGRATION_IN_PROGRESS = 'migration_in_progress'; + public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; /** Realtime */ - public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; - public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages'; - public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation'; + public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; + public const string REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages'; + public const string REALTIME_POLICY_VIOLATION = 'realtime_policy_violation'; /** Health */ - public const HEALTH_QUEUE_SIZE_EXCEEDED = 'health_queue_size_exceeded'; - public const HEALTH_CERTIFICATE_EXPIRED = 'health_certificate_expired'; - public const HEALTH_INVALID_HOST = 'health_invalid_host'; + public const string HEALTH_QUEUE_SIZE_EXCEEDED = 'health_queue_size_exceeded'; + public const string HEALTH_CERTIFICATE_EXPIRED = 'health_certificate_expired'; + public const string HEALTH_INVALID_HOST = 'health_invalid_host'; /** Provider */ - public const PROVIDER_NOT_FOUND = 'provider_not_found'; - public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists'; - public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; - public const PROVIDER_MISSING_CREDENTIALS = 'provider_missing_credentials'; + public const string PROVIDER_NOT_FOUND = 'provider_not_found'; + public const string PROVIDER_ALREADY_EXISTS = 'provider_already_exists'; + public const string PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; + public const string PROVIDER_MISSING_CREDENTIALS = 'provider_missing_credentials'; /** Topic */ - public const TOPIC_NOT_FOUND = 'topic_not_found'; - public const TOPIC_ALREADY_EXISTS = 'topic_already_exists'; + public const string TOPIC_NOT_FOUND = 'topic_not_found'; + public const string TOPIC_ALREADY_EXISTS = 'topic_already_exists'; /** Subscriber */ - public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found'; - public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; + public const string SUBSCRIBER_NOT_FOUND = 'subscriber_not_found'; + public const string SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; /** Message */ - public const MESSAGE_NOT_FOUND = 'message_not_found'; - public const MESSAGE_MISSING_TARGET = 'message_missing_target'; - public const MESSAGE_ALREADY_SENT = 'message_already_sent'; - public const MESSAGE_ALREADY_PROCESSING = 'message_already_processing'; - public const MESSAGE_ALREADY_FAILED = 'message_already_failed'; - public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; - public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email'; - public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms'; - public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push'; - public const MESSAGE_MISSING_SCHEDULE = 'message_missing_schedule'; + public const string MESSAGE_NOT_FOUND = 'message_not_found'; + public const string MESSAGE_MISSING_TARGET = 'message_missing_target'; + public const string MESSAGE_ALREADY_SENT = 'message_already_sent'; + public const string MESSAGE_ALREADY_PROCESSING = 'message_already_processing'; + public const string MESSAGE_ALREADY_FAILED = 'message_already_failed'; + public const string MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; + public const string MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email'; + public const string MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms'; + public const string MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push'; + public const string MESSAGE_MISSING_SCHEDULE = 'message_missing_schedule'; /** Targets */ - public const TARGET_PROVIDER_INVALID_TYPE = 'target_provider_invalid_type'; + public const string TARGET_PROVIDER_INVALID_TYPE = 'target_provider_invalid_type'; /** Schedules */ - public const SCHEDULE_NOT_FOUND = 'schedule_not_found'; + public const string SCHEDULE_NOT_FOUND = 'schedule_not_found'; /** Tokens */ - public const TOKEN_NOT_FOUND = 'token_not_found'; - public const TOKEN_EXPIRED = 'token_expired'; - public const TOKEN_RESOURCE_TYPE_INVALID = 'token_resource_type_invalid'; + public const string TOKEN_NOT_FOUND = 'token_not_found'; + public const string TOKEN_EXPIRED = 'token_expired'; + public const string TOKEN_RESOURCE_TYPE_INVALID = 'token_resource_type_invalid'; protected string $type = ''; protected array $errors = []; @@ -378,7 +378,13 @@ class Exception extends \Exception private array $ctas = []; private ?string $view = null; - public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int|string $code = null, \Throwable $previous = null, ?string $view = null) + public function __construct( + string $type = Exception::GENERAL_UNKNOWN, + string $message = null, + int|string $code = null, + \Throwable $previous = null, + ?string $view = null + ) { $this->errors = Config::getParam('errors'); $this->type = $type; @@ -388,7 +394,7 @@ class Exception extends \Exception // Mark string errors like HY001 from PDO as 500 errors if (\is_string($this->code)) { if (\is_numeric($this->code)) { - $this->code = (int) $this->code; + $this->code = (int)$this->code; } else { $this->code = 500; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 58ec470bd2..63f34241bd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -270,6 +270,12 @@ class Update extends Action ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); + } catch (NotFoundException $e) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + + throw new Exception(Exception::DOCUMENT_NOT_FOUND); } catch (DuplicateException|ConflictException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', From 5eb7c3a7c83e13ca5899fa494f94aac31fbb87d5 Mon Sep 17 00:00:00 2001 From: Veeresh <75656445+Veera-mulge@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:18:25 +0530 Subject: [PATCH 108/385] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 375b1ad48c..e0b5c5ab4b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> We just announced Opt-in relationship loading for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-opt-in-relationship-loading) +> We just announced time helper queries for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-time-helper-queries) > Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga) From de5df4b72ec27a6113c2512dacc318d2d6bbb9f2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 4 Sep 2025 03:57:03 +1200 Subject: [PATCH 109/385] Add extra tests --- app/config/errors.php | 2 +- composer.lock | 46 +- src/Appwrite/Extend/Exception.php | 3 +- .../Databases/Http/Transactions/Update.php | 25 +- .../{ACIDComplianceTest.php => ACIDTest.php} | 10 +- .../DatabasesTransactionsTest.php | 375 ----- .../Transactions/TransactionsTest.php | 1385 +++++++++++++++++ 7 files changed, 1427 insertions(+), 419 deletions(-) rename tests/e2e/Services/Databases/Transactions/{ACIDComplianceTest.php => ACIDTest.php} (99%) delete mode 100644 tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php create mode 100644 tests/e2e/Services/Databases/Transactions/TransactionsTest.php diff --git a/app/config/errors.php b/app/config/errors.php index 098c87c574..e76ca9ff68 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -989,7 +989,7 @@ return [ Exception::TRANSACTION_LIMIT_EXCEEDED => [ 'name' => Exception::TRANSACTION_LIMIT_EXCEEDED, 'description' => 'The maximum number of operations per transaction has been exceeded.', - 'code' => 422, + 'code' => 400, ], Exception::TRANSACTION_NOT_READY => [ 'name' => Exception::TRANSACTION_NOT_READY, diff --git a/composer.lock b/composer.lock index 90bb2f12f6..ed66af8572 100644 --- a/composer.lock +++ b/composer.lock @@ -1515,16 +1515,16 @@ }, { "name": "open-telemetry/sem-conv", - "version": "1.36.0", + "version": "1.37.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a" + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/60dd18fd21d45e6f4234ecab89c14021b6e3de9a", - "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", "shasum": "" }, "require": { @@ -1568,7 +1568,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-04T03:22:08+00:00" + "time": "2025-09-03T12:08:10+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "1.2.4", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "87fb55e86892eecd726635eb1829acb743c2c156" + "reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/87fb55e86892eecd726635eb1829acb743c2c156", - "reference": "87fb55e86892eecd726635eb1829acb743c2c156", + "url": "https://api.github.com/repos/utopia-php/database/zipball/fcd166b715a14cfea11f7a9c47d4c0076bedcecd", + "reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.2.4" + "source": "https://github.com/utopia-php/database/tree/1.3.1" }, - "time": "2025-09-01T06:01:09+00:00" + "time": "2025-09-03T15:50:41+00:00" }, { "name": "utopia-php/detector", @@ -3942,16 +3942,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.22", + "version": "0.33.23", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "c01a815cb976c9255e045fc3bcc3f5fcf477e0bc" + "reference": "88e8002365c10a727014ecc56322bcd1d780ceed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/c01a815cb976c9255e045fc3bcc3f5fcf477e0bc", - "reference": "c01a815cb976c9255e045fc3bcc3f5fcf477e0bc", + "url": "https://api.github.com/repos/utopia-php/http/zipball/88e8002365c10a727014ecc56322bcd1d780ceed", + "reference": "88e8002365c10a727014ecc56322bcd1d780ceed", "shasum": "" }, "require": { @@ -3983,9 +3983,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.22" + "source": "https://github.com/utopia-php/http/tree/0.33.23" }, - "time": "2025-08-26T10:29:50+00:00" + "time": "2025-09-03T11:58:14+00:00" }, { "name": "utopia-php/image", @@ -5007,16 +5007,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.1.15", + "version": "1.1.16", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "8e8e39634ba7558704522959d88f3542563a5444" + "reference": "f8fbc4b1ba0e918825338f50cbdea4d887389c41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/8e8e39634ba7558704522959d88f3542563a5444", - "reference": "8e8e39634ba7558704522959d88f3542563a5444", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f8fbc4b1ba0e918825338f50cbdea4d887389c41", + "reference": "f8fbc4b1ba0e918825338f50cbdea4d887389c41", "shasum": "" }, "require": { @@ -5052,9 +5052,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.1.15" + "source": "https://github.com/appwrite/sdk-generator/tree/1.1.16" }, - "time": "2025-08-27T04:59:35+00:00" + "time": "2025-09-03T06:50:04+00:00" }, { "name": "doctrine/annotations", diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index cfa55c540e..2047cfc044 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -384,8 +384,7 @@ class Exception extends \Exception int|string $code = null, \Throwable $previous = null, ?string $view = null - ) - { + ) { $this->errors = Config::getParam('errors'); $this->type = $type; $this->view = $view; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 63f34241bd..9d4eae41c9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -14,6 +14,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Query; use Utopia\Database\Validator\UID; @@ -167,12 +168,17 @@ class Update extends Action break; case 'update': - $document = new Document($data); - $state[$collectionId][$documentId] = $dbForProject->updateDocument( + $document = $dbForProject->updateDocument( $collectionId, $documentId, - $document, + new Document($data), ); + + if ($document->isEmpty()) { + throw new ConflictException(''); + } + + $state[$collectionId][$documentId] = $document; break; case 'upsert': @@ -258,11 +264,7 @@ class Update extends Action $transaction = $dbForProject->updateDocument( 'transactions', $transactionId, - new Document( - [ - 'status' => 'committed', - ] - ) + new Document(['status' => 'committed']) ); // Clear the transaction logs @@ -270,23 +272,20 @@ class Update extends Action ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); - } catch (NotFoundException $e) { + } catch (NotFoundException) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); - throw new Exception(Exception::DOCUMENT_NOT_FOUND); - } catch (DuplicateException|ConflictException $e) { + } catch (DuplicateException|ConflictException) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); - throw new Exception(Exception::TRANSACTION_CONFLICT); } catch (TransactionException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); - throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); } }); diff --git a/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php b/tests/e2e/Services/Databases/Transactions/ACIDTest.php similarity index 99% rename from tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php rename to tests/e2e/Services/Databases/Transactions/ACIDTest.php index 42a2967685..7bf027b828 100644 --- a/tests/e2e/Services/Databases/Transactions/ACIDComplianceTest.php +++ b/tests/e2e/Services/Databases/Transactions/ACIDTest.php @@ -12,7 +12,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -class ACIDComplianceTest extends Scope +class ACIDTest extends Scope { use ProjectCustom; use SideClient; @@ -20,7 +20,7 @@ class ACIDComplianceTest extends Scope /** * Test atomicity - all operations succeed or all fail */ - public function testTransactionAtomicity(): void + public function testAtomicity(): void { // Create database $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -175,7 +175,7 @@ class ACIDComplianceTest extends Scope /** * Test consistency - schema validation and constraints */ - public function testTransactionConsistency(): void + public function testConsistency(): void { // Create database $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -306,7 +306,7 @@ class ACIDComplianceTest extends Scope /** * Test isolation - concurrent transactions on same data */ - public function testTransactionIsolation(): void + public function testIsolation(): void { // Create database $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -463,7 +463,7 @@ class ACIDComplianceTest extends Scope /** * Test durability - committed data persists */ - public function testTransactionDurability(): void + public function testDurability(): void { // Create database $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ diff --git a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php b/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php deleted file mode 100644 index 513051fcc0..0000000000 --- a/tests/e2e/Services/Databases/Transactions/DatabasesTransactionsTest.php +++ /dev/null @@ -1,375 +0,0 @@ -client->call(Client::METHOD_POST, '/databases', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'TransactionTestDatabase' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Test creating a transaction with default TTL - $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertArrayHasKey('$id', $response['body']); - $this->assertArrayHasKey('status', $response['body']); - $this->assertArrayHasKey('operations', $response['body']); - $this->assertArrayHasKey('expiresAt', $response['body']); - $this->assertEquals('pending', $response['body']['status']); - $this->assertEquals(0, $response['body']['operations']); - - $transactionId1 = $response['body']['$id']; - - // Test creating a transaction with custom TTL - $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'ttl' => 900 - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals('pending', $response['body']['status']); - - $expiresAt = new \DateTime($response['body']['expiresAt']); - $now = new \DateTime(); - $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); - $this->assertGreaterThan(800, $diff); - $this->assertLessThan(1000, $diff); - - $transactionId2 = $response['body']['$id']; - - // Test invalid TTL values - $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'ttl' => 30 // Below minimum - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'ttl' => 4000 // Above maximum - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - return [ - 'databaseId' => $databaseId, - 'transactionId1' => $transactionId1, - 'transactionId2' => $transactionId2 - ]; - } - - /** - * @depends testCreate - */ - public function testAddOperations(array $data): array - { - $databaseId = $data['databaseId']; - $transactionId = $data['transactionId1']; - - // Create a collection for testing - $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TransactionOperationsTest', - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - // Add attributes - $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); - - $this->assertEquals(202, $attribute['headers']['status-code']); - - // Wait for attribute to be created - sleep(2); - - // Add valid operations - $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc1', - 'data' => [ - 'name' => 'Test Document 1' - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc2', - 'data' => [ - 'name' => 'Test Document 2' - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(2, $response['body']['operations']); - - // Test adding more operations - $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'doc1', - 'data' => [ - 'name' => 'Updated Document 1' - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['operations']); - - // Test invalid database ID - $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => 'invalid_database', - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); - - // Test invalid collection ID - $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => 'invalid_collection', - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - - return array_merge($data, [ - 'collectionId' => $collectionId - ]); - } - - /** - * @depends testAddOperations - */ - public function testCommit(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $transactionId = $data['transactionId1']; - - // Commit the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('committed', $response['body']['status']); - - // Verify documents were created - $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(2, $documents['body']['total']); - - // Verify the update was applied - $doc1Found = false; - foreach ($documents['body']['documents'] as $doc) { - if ($doc['$id'] === 'doc1') { - $this->assertEquals('Updated Document 1', $doc['name']); - $doc1Found = true; - } - } - $this->assertTrue($doc1Found, 'Document doc1 should exist with updated name'); - - // Test committing already committed transaction - $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * @depends testCreate - */ - public function testRollback(array $data): void - { - $databaseId = $data['databaseId']; - $transactionId = $data['transactionId2']; - - // Create a collection for rollback test - $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TransactionRollbackTest', - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Add attribute - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'value', - 'size' => 256, - 'required' => true, - ]); - - sleep(2); - - // Add operations - $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'rollback_doc', - 'data' => [ - 'value' => 'Should not exist' - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Rollback the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rollback' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('rolledBack', $response['body']['status']); - - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(0, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php new file mode 100644 index 0000000000..cb0f3f3827 --- /dev/null +++ b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php @@ -0,0 +1,1385 @@ +client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Test creating a transaction with default TTL + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertArrayHasKey('$id', $response['body']); + $this->assertArrayHasKey('status', $response['body']); + $this->assertArrayHasKey('operations', $response['body']); + $this->assertArrayHasKey('expiresAt', $response['body']); + $this->assertEquals('pending', $response['body']['status']); + $this->assertEquals(0, $response['body']['operations']); + + $transactionId1 = $response['body']['$id']; + + // Test creating a transaction with custom TTL + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 900 + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals('pending', $response['body']['status']); + + $expiresAt = new \DateTime($response['body']['expiresAt']); + $now = new \DateTime(); + $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); + $this->assertGreaterThan(800, $diff); + $this->assertLessThan(1000, $diff); + + $transactionId2 = $response['body']['$id']; + + // Test invalid TTL values + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 30 // Below minimum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 4000 // Above maximum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + return [ + 'databaseId' => $databaseId, + 'transactionId1' => $transactionId1, + 'transactionId2' => $transactionId2 + ]; + } + + /** + * @depends testCreate + */ + public function testAddOperations(array $data): array + { + $databaseId = $data['databaseId']; + $transactionId = $data['transactionId1']; + + // Create a collection for testing + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionOperationsTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add attributes + $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + + // Wait for attribute to be created + sleep(2); + + // Add valid operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Test Document 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc2', + 'data' => [ + 'name' => 'Test Document 2' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['operations']); + + // Test adding more operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Updated Document 1' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['operations']); + + // Test invalid database ID + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => 'invalid_database', + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); + + // Test invalid collection ID + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => 'invalid_collection', + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + return array_merge($data, [ + 'collectionId' => $collectionId + ]); + } + + /** + * @depends testAddOperations + */ + public function testCommit(array $data): void + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + $transactionId = $data['transactionId1']; + + // Commit the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('committed', $response['body']['status']); + + // Verify documents were created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(2, $documents['body']['total']); + + // Verify the update was applied + $doc1Found = false; + foreach ($documents['body']['documents'] as $doc) { + if ($doc['$id'] === 'doc1') { + $this->assertEquals('Updated Document 1', $doc['name']); + $doc1Found = true; + } + } + $this->assertTrue($doc1Found, 'Document doc1 should exist with updated name'); + + // Test committing already committed transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * @depends testCreate + */ + public function testRollback(array $data): void + { + $databaseId = $data['databaseId']; + $transactionId = $data['transactionId2']; + + // Create a collection for rollback test + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionRollbackTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Add operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'rollback_doc', + 'data' => [ + 'value' => 'Should not exist' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Rollback the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('rolledBack', $response['body']['status']); + + // Verify no documents were created + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(0, $documents['body']['total']); + } + + /** + * Test transaction expiration + */ + public function testTransactionExpiration(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ExpirationTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction with minimum TTL (60 seconds) + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 60 + ]); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Add operation + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['data' => 'Should expire'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Verify transaction was created with correct expiration + $txnDetails = $this->client->call(Client::METHOD_GET, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $txnDetails['headers']['status-code']); + $this->assertEquals('pending', $txnDetails['body']['status']); + + // Verify expiration time is approximately 60 seconds from now + $expiresAt = new \DateTime($txnDetails['body']['expiresAt']); + $now = new \DateTime(); + $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); + $this->assertGreaterThan(55, $diff); + $this->assertLessThan(65, $diff); + } + + /** + * Test maximum operations per transaction + */ + public function testTransactionSizeLimit(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'SizeLimitTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [Permission::create(Role::any())], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Try to add operations exceeding the limit (assuming limit is 100) + // We'll add 50 operations twice to test incremental limit + $operations = []; + for ($i = 0; $i < 50; $i++) { + $operations[] = [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc_' . $i, + 'data' => ['value' => 'Test ' . $i] + ]; + } + + // First batch should succeed + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => $operations + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(50, $response['body']['operations']); + + // Second batch of 50 more operations + $operations = []; + for ($i = 50; $i < 100; $i++) { + $operations[] = [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'doc_' . $i, + 'action' => 'create', + 'data' => ['value' => 'Test ' . $i] + ]; + } + + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => $operations + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(100, $response['body']['operations']); + + // Try to add one more operation - should fail + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc_overflow', + 'data' => ['value' => 'This should fail'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test concurrent transactions with conflicting operations + */ + public function testConcurrentTransactionConflicts(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ConflictTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000, + ]); + + sleep(2); + + // Create initial document + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'shared_doc', + 'data' => ['counter' => 100] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create two transactions + $txn1 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $txn2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId1 = $txn1['body']['$id']; + $transactionId2 = $txn2['body']['$id']; + + // Both transactions try to update the same document + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId1}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'shared_doc', + 'data' => ['counter' => 200] + ] + ] + ]); + + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'shared_doc', + 'data' => ['counter' => 300] + ] + ] + ]); + + // Commit first transaction + $response1 = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId1}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response1['headers']['status-code']); + + // Commit second transaction - should fail with conflict + $response2 = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response2['headers']['status-code']); // Conflict + + // Verify the document has the value from first transaction + $doc = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $doc['body']['counter']); + } + + /** + * Test deleting a document that's being updated in a transaction + */ + public function testDeleteDocumentDuringTransaction(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DeleteConflictDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create document + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'target_doc', + 'data' => ['data' => 'Original'] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add update operation to transaction + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'target_doc', + 'data' => ['data' => 'Updated in transaction'] + ] + ] + ]); + + // Delete the document outside of transaction + $response = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents/target_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(204, $response['headers']['status-code']); + + // Try to commit transaction - should fail because document no longer exists + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response['headers']['status-code']); // Conflict + } + + /** + * Test bulk operations in transactions + */ + public function testBulkOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkOpsDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); + + // Create some initial documents + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'category' => 'old' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + // Bulk create + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkCreate', + 'data' => [ + ['$id' => 'bulk_1', 'name' => 'Bulk 1', 'category' => 'new'], + ['$id' => 'bulk_2', 'name' => 'Bulk 2', 'category' => 'new'], + ['$id' => 'bulk_3', 'name' => 'Bulk 3', 'category' => 'new'], + ] + ], + // Bulk update + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [Query::equal('category', ['old'])->toString()], + 'data' => ['category' => 'updated'] + ] + ], + // Bulk delete + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [Query::equal('name', ['Existing 5'])->toString()] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify results + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have 7 documents (5 existing - 1 deleted + 3 new) + $this->assertEquals(7, $documents['body']['total']); + + // Check categories were updated + $oldCategoryCount = 0; + $updatedCategoryCount = 0; + $newCategoryCount = 0; + + foreach ($documents['body']['documents'] as $doc) { + switch ($doc['category']) { + case 'old': + $oldCategoryCount++; + break; + case 'updated': + $updatedCategoryCount++; + break; + case 'new': + $newCategoryCount++; + break; + } + } + + $this->assertEquals(0, $oldCategoryCount); + $this->assertEquals(4, $updatedCategoryCount); // 4 existing docs updated + $this->assertEquals(3, $newCategoryCount); // 3 new docs + } + + /** + * Test transaction with mixed success and failure operations + */ + public function testPartialFailureRollback(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'PartialFailureDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes with constraints + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); + + // Create unique index on email + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'unique_email', + 'type' => 'unique', + 'attributes' => ['email'], + ]); + + sleep(3); + + // Create an existing document + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => ID::unique(), + 'data' => ['email' => 'existing@example.com'] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operations - mix of valid and invalid + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid1@example.com'] // Valid + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid2@example.com'] // Valid + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'existing@example.com'] // Will fail - duplicate + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid3@example.com'] // Would be valid but should rollback + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Try to commit - should fail and rollback all operations + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response['headers']['status-code']); // Conflict due to duplicate + + // Verify NO new documents were created (atomicity) + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(1, $documents['body']['total']); // Only the original document + $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + } + + /** + * Test double commit/rollback attempts + */ + public function testDoubleCommitRollback(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DoubleCommitDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [Permission::create(Role::any())], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Test double commit + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operation + $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['data' => 'Test'] + ] + ] + ]); + + // First commit + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Second commit attempt - should fail + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already committed + + // Test double rollback + $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId2 = $transaction2['body']['$id']; + + // First rollback + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Second rollback attempt - should fail + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already rolled back + } + + /** + * Test operations on non-existent documents + */ + public function testOperationsOnNonExistentDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'NonExistentDocDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Try to update non-existent document + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'non_existent_doc', + 'data' => ['data' => 'Should fail'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); // Operation added + + // Commit should fail + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); // Document not found + + // Test delete non-existent document + $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId2 = $transaction2['body']['$id']; + + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'delete', + 'documentId' => 'non_existent_doc', + 'data' => [] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit should fail + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); // Document not found + } +} From ba97cfb440332d8f735d8cb373a86f0d787893db Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Wed, 3 Sep 2025 22:59:20 +0000 Subject: [PATCH 110/385] chore: create workflow to auto add labels to issues --- .github/labeler.yml | 83 ++++++++++++++++++++++++++ .github/workflows/auto-label-issue.yml | 22 +++++++ 2 files changed, 105 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/auto-label-issue.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..fb46eb5ba1 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,83 @@ +# Fixes and upgrades for the Appwrite Auth / Users / Teams services. +"product / auth": + - "(auth|session|login|logout|register|2fa|mfa|users|teams|memberships|invite|oauth|oauth2|sso|jwt)" + +# Fixes and upgrades for the Appwrite Realtime API. +"api / realtime": + - "(realtime|subscribe|websockets)" + +# Console, UI and UX issues +"product / console": + - "(console)" + +# Fixes and upgrades for the Appwrite Storage. +"product / storage": + - "(storage|bucket|file|image|preview|download)" + +# Fixes and upgrades for the Appwrite Database. +"product / databases": + - "(database|collection|tables|attribute|column|document|row|query|queries|indexes|search|filter|sort|pagination)" + +# Fixes and upgrades for the Appwrite Functions. +"product / functions": + - "(function|runtime|deployment|execution|trigger|cron|schedule)" + +# Fixes and upgrades for the Appwrite Docs. +# "product / docs": +# - + +# Fixes and upgrades for the Appwrite Migrations. +"product / migrations": + - "(migrate|migration)" + +# Fixes and upgrades for the Appwrite Messaging. +"product / messaging": + - "(messaging|email|sms|push|provider|topic|target|notification)" + +# Fixes and upgrades for the Appwrite Platform. +# "product / platform": +# - + +# Fixes and upgrades for database relationships +"feature / relationships": + - "(relationship)" + +# Issues found only on Appwrite Cloud +# "product / cloud": +# - + +# Fixes and upgrades for the Appwrite VCS. +"product / vcs": + - "(repo|push|vcs|repository)" + +# Fixes and upgrades for the Appwrite GraphQL API. +"api / graphql": + - "(graphql|gql|mutation)" + +# Fixes and upgrades for the Appwrite Assistant. +"product / assistant": + - "(assistant)" + +# Fixes and upgrades for the Appwrite Domains. +"product / domains": + - "(domain|dns|ssl|certificate)" + +# Fixes and upgrades for the Appwrite Locale. +"product / locale": + - "(locale|i18n|internationalization|localization|l10n|translation|timezone|country)" + +# Fixes and upgrades for the Appwrite Avatars. +"product / avatars": + - "(avatar|initial|flag|icon)" + +# Fixes and upgrades for Appwrite Sites. +"product / sites": + - "(site|web|hosting|domain|ssl|certificate|nextjs|nuxt|react|angular|vue|svelte|astro)" + +# Fixes and upgrades for the Appwrite CLI. +"sdk / cli": + - "(cli|command line)" + +# Issues only found when self-hosting Appwrite +"product / self-hosted": + - "(self-host|self host)" diff --git a/.github/workflows/auto-label-issue.yml b/.github/workflows/auto-label-issue.yml new file mode 100644 index 0000000000..e0eb0de98d --- /dev/null +++ b/.github/workflows/auto-label-issue.yml @@ -0,0 +1,22 @@ +name: Auto Label Issue + +on: + issues: + types: [opened] + +permissions: + issues: write + contents: read + +jobs: + labeler: + runs-on: ubuntu-latest + steps: + - name: Issue Labeler + uses: github/issue-labeler@v3.4 + with: + configuration-path: .github/labeler.yml + enable-versioned-regex: false + include-title: 1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7eba6582c6128040cb1a5674ec4f09c486610790 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 4 Sep 2025 20:32:37 +1200 Subject: [PATCH 111/385] Fix bulk payloads --- .../Databases/Http/Transactions/Update.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index 9d4eae41c9..b8b77d79ef 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -223,10 +223,9 @@ class Update extends Action case 'bulkCreate': $dbForProject->createDocuments( $collectionId, - array_map(fn ($data) => new Document($data), $data), + $data, onNext: function (Document $document) use (&$state, $collectionId) { $state[$collectionId][$document->getId()] = $document; - } ); break; @@ -234,7 +233,7 @@ class Update extends Action case 'bulkUpdate': $dbForProject->updateDocuments( $collectionId, - $data['data'] ?? null, + new Document($data['data']), Query::parseQueries($data['queries'] ?? []) ); break; @@ -242,10 +241,9 @@ class Update extends Action case 'bulkUpsert': $dbForProject->createOrUpdateDocuments( $collectionId, - array_map(fn ($data) => new Document($data), $data), + $data, onNext: function (Document $document) use (&$state, $collectionId) { $state[$collectionId][$document->getId()] = $document; - } ); break; @@ -272,16 +270,16 @@ class Update extends Action ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); - } catch (NotFoundException) { + } catch (NotFoundException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); - throw new Exception(Exception::DOCUMENT_NOT_FOUND); - } catch (DuplicateException|ConflictException) { + throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e); + } catch (DuplicateException|ConflictException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ])); - throw new Exception(Exception::TRANSACTION_CONFLICT); + throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); } catch (TransactionException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', From d28643bd2bdc0f9dfaed5985f1c7ba83b37540d5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 5 Sep 2025 01:02:30 +1200 Subject: [PATCH 112/385] Update DB --- composer.json | 2 +- composer.lock | 68 +-- .../Collections/Documents/Bulk/Upsert.php | 2 +- .../Collections/Documents/Upsert.php | 2 +- .../Databases/Http/Transactions/Update.php | 539 +++++++++++++----- .../Platform/Workers/StatsResources.php | 2 +- src/Appwrite/Platform/Workers/StatsUsage.php | 4 +- .../Transactions/TransactionsTest.php | 4 +- 8 files changed, 433 insertions(+), 190 deletions(-) diff --git a/composer.json b/composer.json index 0c662c775f..7d9176d2aa 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "1.*", + "utopia-php/database": "2.*", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index ed66af8572..bf76f13a3e 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": "0da713ee5642eba1d30bc51c1a04a723", + "content-hash": "3565fcc2471b5d18a159b6da1c8fad31", "packages": [ { "name": "adhocore/jwt", @@ -3296,16 +3296,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be" + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/c5e2232033b507a07f72180dc56d37e1872ee7be", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a", + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a", "shasum": "" }, "require": { @@ -3313,7 +3313,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3341,9 +3341,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.0.0" + "source": "https://github.com/utopia-php/abuse/tree/1.0.1" }, - "time": "2025-08-13T09:12:54+00:00" + "time": "2025-09-04T12:46:54+00:00" }, { "name": "utopia-php/analytics", @@ -3393,21 +3393,21 @@ }, { "name": "utopia-php/audit", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb" + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/c0ed75f4d068f1f6c2e7149a909490d4214e72bb", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3434,9 +3434,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/1.0.0" + "source": "https://github.com/utopia-php/audit/tree/1.0.1" }, - "time": "2025-08-13T09:09:00+00:00" + "time": "2025-09-04T12:46:43+00:00" }, { "name": "utopia-php/cache", @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "1.3.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd" + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/fcd166b715a14cfea11f7a9c47d4c0076bedcecd", - "reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.3.1" + "source": "https://github.com/utopia-php/database/tree/2.0.0" }, - "time": "2025-09-03T15:50:41+00:00" + "time": "2025-09-04T12:36:53+00:00" }, { "name": "utopia-php/detector", @@ -3942,16 +3942,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.23", + "version": "0.33.24", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "88e8002365c10a727014ecc56322bcd1d780ceed" + "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/88e8002365c10a727014ecc56322bcd1d780ceed", - "reference": "88e8002365c10a727014ecc56322bcd1d780ceed", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5112b1023342163e3fbedec99f38fc32c8700aa0", + "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0", "shasum": "" }, "require": { @@ -3983,9 +3983,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.23" + "source": "https://github.com/utopia-php/http/tree/0.33.24" }, - "time": "2025-09-03T11:58:14+00:00" + "time": "2025-09-04T04:18:39+00:00" }, { "name": "utopia-php/image", @@ -4190,16 +4190,16 @@ }, { "name": "utopia-php/migration", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "38171023efd3abe650d2abc5ac65f5df52311da6" + "reference": "eb60a61934be1d6f2f4fdabd9903a841ba078bc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6", - "reference": "38171023efd3abe650d2abc5ac65f5df52311da6", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/eb60a61934be1d6f2f4fdabd9903a841ba078bc9", + "reference": "eb60a61934be1d6f2f4fdabd9903a841ba078bc9", "shasum": "" }, "require": { @@ -4207,7 +4207,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "1.*", + "utopia-php/database": "2.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4240,9 +4240,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.0.1" + "source": "https://github.com/utopia-php/migration/tree/1.0.2" }, - "time": "2025-08-28T13:41:25+00:00" + "time": "2025-09-04T12:47:05+00:00" }, { "name": "utopia-php/orchestration", diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 2debdfcf6d..618d640d95 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -161,7 +161,7 @@ class Upsert extends Action try { $modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) { - return $dbForProject->createOrUpdateDocuments( + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documents, onNext: function (Document $document) use ($plan, &$upserted) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index c117691348..7a6c7e960b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -299,7 +299,7 @@ class Upsert extends Action $upserted = []; try { $dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) { - return $dbForProject->createOrUpdateDocuments( + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), [$newDocument], onNext: function (Document $document) use (&$upserted) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index b8b77d79ef..ed277e7b2f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -12,9 +12,11 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\NotFound as NotFoundException; +use Utopia\Database\Exception\Structure; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Query; use Utopia\Database\Validator\UID; @@ -65,6 +67,23 @@ class Update extends Action ->callback($this->action(...)); } + /** + * @param string $transactionId + * @param bool $commit + * @param bool $rollback + * @param UtopiaResponse $response + * @param Database $dbForProject + * @param Delete $queueForDeletes + * @return void + * @throws ConflictException + * @throws Exception + * @throws \DateMalformedStringException + * @throws \Throwable + * @throws \Utopia\Database\Exception + * @throws Authorization + * @throws Structure + * @throws \Utopia\Exception + */ public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Delete $queueForDeletes): void { if (!$commit && !$rollback) { @@ -113,149 +132,38 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; - // Check if this operation depends on documents created in same transaction - $dependent = \in_array($action, ['update', 'increment', 'decrement']) - && isset($state[$collectionId][$documentId]); - - if ($dependent) { - // Don't use timestamp wrapper for dependent operations - switch ($action) { - case 'update': - // Update the state document directly - $existing = $state[$collectionId][$documentId]; - foreach ($data as $key => $value) { - $existing->setAttribute($key, $value); - } - $state[$collectionId][$documentId] = $dbForProject->updateDocument( - $collectionId, - $documentId, - $existing - ); - break; - - case 'increment': - $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( - collection: $collectionId, - id: $documentId, - attribute: $data['attribute'], - value: $data['value'] ?? 1, - max: $data['max'] ?? null - ); - break; - - case 'decrement': - $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( - collection: $collectionId, - id: $documentId, - attribute: $data['attribute'], - value: $data['value'] ?? 1, - min: $data['min'] ?? null - ); - break; - } - } else { - // Use timestamp wrapper for independent operations - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $queueForDeletes, $action, $collectionId, $documentId, $data, &$state) { - switch ($action) { - case 'create': - if ($documentId && !isset($data['$id'])) { - $data['$id'] = $documentId; - } - $state[$collectionId][$documentId] = $dbForProject->createDocument( - $collectionId, - new Document($data), - ); - break; - - case 'update': - $document = $dbForProject->updateDocument( - $collectionId, - $documentId, - new Document($data), - ); - - if ($document->isEmpty()) { - throw new ConflictException(''); - } - - $state[$collectionId][$documentId] = $document; - break; - - case 'upsert': - $document = new Document($data); - $dbForProject->createOrUpdateDocuments( - $collectionId, - [$document], - onNext: function (Document $document) use (&$state, $collectionId) { - $state[$collectionId][$document->getId()] = $document; - } - ); - break; - - case 'delete': - $dbForProject->deleteDocument($collectionId, $documentId); - - if (isset($state[$collectionId][$documentId])) { - unset($state[$collectionId][$documentId]); - } - break; - - case 'increment': - $dbForProject->increaseDocumentAttribute( - collection: $collectionId, - id: $documentId, - attribute: $data['attribute'], - value: $data['value'] ?? 1, - max: $data['max'] ?? null - ); - break; - - case 'decrement': - $dbForProject->decreaseDocumentAttribute( - collection: $collectionId, - id: $documentId, - attribute: $data['attribute'], - value: $data['value'] ?? 1, - min: $data['min'] ?? null - ); - break; - - case 'bulkCreate': - $dbForProject->createDocuments( - $collectionId, - $data, - onNext: function (Document $document) use (&$state, $collectionId) { - $state[$collectionId][$document->getId()] = $document; - } - ); - break; - - case 'bulkUpdate': - $dbForProject->updateDocuments( - $collectionId, - new Document($data['data']), - Query::parseQueries($data['queries'] ?? []) - ); - break; - - case 'bulkUpsert': - $dbForProject->createOrUpdateDocuments( - $collectionId, - $data, - onNext: function (Document $document) use (&$state, $collectionId) { - $state[$collectionId][$document->getId()] = $document; - } - ); - break; - - case 'bulkDelete': - $dbForProject->deleteDocuments( - $collectionId, - Query::parseQueries($data['queries'] ?? []) - ); - break; - } - }); + // Execute the operation based on its type + switch ($action) { + case 'create': + $this->handleCreateOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); + break; + case 'update': + $this->handleUpdateOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); + break; + case 'upsert': + $this->handleUpsertOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); + break; + case 'delete': + $this->handleDeleteOperation($dbForProject, $collectionId, $documentId, $createdAt, $state); + break; + case 'increment': + $this->handleIncrementOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); + break; + case 'decrement': + $this->handleDecrementOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); + break; + case 'bulkCreate': + $this->handleBulkCreateOperation($dbForProject, $collectionId, $data, $createdAt, $state); + break; + case 'bulkUpdate': + $this->handleBulkUpdateOperation($dbForProject, $collectionId, $data, $createdAt, $state); + break; + case 'bulkUpsert': + $this->handleBulkUpsertOperation($dbForProject, $collectionId, $data, $createdAt, $state); + break; + case 'bulkDelete': + $this->handleBulkDeleteOperation($dbForProject, $collectionId, $data, $createdAt, $state); + break; } } @@ -269,7 +177,6 @@ class Update extends Action $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); - } catch (NotFoundException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', @@ -290,17 +197,351 @@ class Update extends Action } if ($rollback) { - $queueForDeletes - ->setType(DELETE_TYPE_DOCUMENT) - ->setDocument($transaction); - $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'rolledBack', ])); + + $queueForDeletes + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($transaction); } $response ->setStatusCode(SwooleResponse::STATUS_CODE_OK) ->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION); } -} + + /** + * Handle create operation + * @throws \Utopia\Database\Exception + */ + private function handleCreateOperation( + Database $dbForProject, + string $collectionId, + ?string $documentId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + if ($documentId && !isset($data['$id'])) { + $data['$id'] = $documentId; + } + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { + $state[$collectionId][$data['$id']] = $dbForProject->createDocument( + $collectionId, + new Document($data), + ); + }); + } + + /** + * Handle update operation + * @throws ConflictException + * @throws \Utopia\Database\Exception + */ + private function handleUpdateOperation( + Database $dbForProject, + string $collectionId, + string $documentId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $dependent = isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Update the state document directly without timestamp wrapper + $state[$collectionId][$documentId] = $dbForProject->updateDocument( + $collectionId, + $documentId, + new Document($data), + ); + return; + } + + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $document = $dbForProject->updateDocument( + $collectionId, + $documentId, + new Document($data), + ); + if ($document->isEmpty()) { + throw new ConflictException(''); + } + $state[$collectionId][$documentId] = $document; + }); + } + + /** + * Handle upsert operation + * @throws \Utopia\Database\Exception + */ + private function handleUpsertOperation( + Database $dbForProject, + string $collectionId, + ?string $documentId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $dependent = isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Upsert the state document directly without timestamp wrapper + $state[$collectionId][$documentId] = $dbForProject->upsertDocument( + $collectionId, + new Document($data), + ); + return; + } + + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $state[$collectionId][$documentId] = $dbForProject->upsertDocument( + $collectionId, + new Document($data), + ); + }); + } + + /** + * Handle delete operation + */ + private function handleDeleteOperation( + Database $dbForProject, + string $collectionId, + string $documentId, + \DateTime $createdAt, + array &$state + ): void + { + $dependent = isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Delete without timestamp wrapper + $dbForProject->deleteDocument($collectionId, $documentId); + unset($state[$collectionId][$documentId]); + return; + } + + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, &$state) { + $dbForProject->deleteDocument($collectionId, $documentId); + if (isset($state[$collectionId][$documentId])) { + unset($state[$collectionId][$documentId]); + } + }); + } + + /** + * Handle increment operation + */ + private function handleIncrementOperation( + Database $dbForProject, + string $collectionId, + string $documentId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $dependent = isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Increment without timestamp wrapper + $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + max: $data['max'] ?? null + ); + return; + } + + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data) { + $dbForProject->increaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + max: $data['max'] ?? null + ); + }); + } + + /** + * Handle decrement operation + */ + private function handleDecrementOperation( + Database $dbForProject, + string $collectionId, + string $documentId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $dependent = isset($state[$collectionId][$documentId]); + + if ($dependent) { + // Decrement without timestamp wrapper + $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + min: $data['min'] ?? null + ); + return; + } + + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data) { + $dbForProject->decreaseDocumentAttribute( + collection: $collectionId, + id: $documentId, + attribute: $data['attribute'], + value: $data['value'] ?? 1, + min: $data['min'] ?? null + ); + }); + } + + /** + * Handle bulk create operation + */ + private function handleBulkCreateOperation( + Database $dbForProject, + string $collectionId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { + $dbForProject->createDocuments( + $collectionId, + $data, + onNext: function (Document $document) use (&$state, $collectionId) { + $state[$collectionId][$document->getId()] = $document; + } + ); + }); + } + + /** + * Handle bulk update operation with manual timestamp checking + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + * @throws ConflictException + */ + private function handleBulkUpdateOperation( + Database $dbForProject, + string $collectionId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $queries = Query::parseQueries($data['queries'] ?? []); + + $dbForProject->updateDocuments( + $collectionId, + new Document($data['data']), + $queries, + onNext: function (Document $updated, Document $old) use (&$state, $collectionId, $createdAt) { + // Check if this document was created/modified in this transaction + $dependent = isset($state[$collectionId][$updated->getId()]); + + // If not in transaction state, check for timestamp conflicts + if (!$dependent) { + $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); + if ($oldUpdatedAt > $createdAt) { + throw new ConflictException('Document was updated after the request timestamp'); + } + } + + $state[$collectionId][$updated->getId()] = $updated; + } + ); + } + + /** + * Handle bulk upsert operation with manual timestamp checking + * @throws ConflictException + */ + private function handleBulkUpsertOperation( + Database $dbForProject, + string $collectionId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + // Run bulk upsert without timestamp wrapper, checking manually in callback + $dbForProject->upsertDocuments( + $collectionId, + $data, + onNext: function (Document $upserted, ?Document $old) use (&$state, $collectionId, $createdAt) { + if ($old !== null) { + // This is an update - check if document was created/modified in this transaction + $dependent = isset($state[$collectionId][$upserted->getId()]); + + // If not in transaction state, check for timestamp conflicts + if (!$dependent) { + $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); + if ($oldUpdatedAt > $createdAt) { + throw new ConflictException('Document was updated after the request timestamp'); + } + } + } + + // If $old is null, this is a create operation - no timestamp check needed + $state[$collectionId][$upserted->getId()] = $upserted; + } + ); + } + + /** + * Handle bulk delete operation with manual timestamp checking + * @throws \Utopia\Database\Exception\Query + * @throws ConflictException + */ + private function handleBulkDeleteOperation( + Database $dbForProject, + string $collectionId, + array $data, + \DateTime $createdAt, + array &$state + ): void + { + $queries = Query::parseQueries($data['queries'] ?? []); + + $dbForProject->deleteDocuments( + $collectionId, + $queries, + onNext: function (Document $deleted, Document $old) use (&$state, $collectionId, $createdAt) { + $dependent = isset($state[$collectionId][$deleted->getId()]); + + // If not in transaction state, check for timestamp conflicts + if (!$dependent) { + $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); + if ($oldUpdatedAt > $createdAt) { + throw new ConflictException('Document was updated after the transaction operation'); + } + } + + // Remove from state after successful deletion + if (isset($state[$collectionId][$deleted->getId()])) { + unset($state[$collectionId][$deleted->getId()]); + } + } + ); + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Workers/StatsResources.php b/src/Appwrite/Platform/Workers/StatsResources.php index 98c9d01a87..6f334437b0 100644 --- a/src/Appwrite/Platform/Workers/StatsResources.php +++ b/src/Appwrite/Platform/Workers/StatsResources.php @@ -432,7 +432,7 @@ class StatsResources extends Action protected function writeDocuments(Database $dbForLogs, Document $project): void { - $dbForLogs->createOrUpdateDocuments( + $dbForLogs->upsertDocuments( 'stats', $this->documents ); diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 3610381d5a..3a615072df 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -424,7 +424,7 @@ class StatsUsage extends Action try { $dbForProject = $getProjectDB($projectStats['project']); Console::log('Processing batch with ' . count($projectStats['stats']) . ' stats'); - $dbForProject->createOrUpdateDocumentsWithIncrease('stats', 'value', $projectStats['stats']); + $dbForProject->upsertDocumentsWithIncrease('stats', 'value', $projectStats['stats']); Console::success('Batch successfully written to DB'); unset($this->projects[$sequence]); @@ -468,7 +468,7 @@ class StatsUsage extends Action try { Console::log('Processing batch with ' . count($this->statDocuments) . ' stats'); - $dbForLogs->createOrUpdateDocumentsWithIncrease( + $dbForLogs->upsertDocumentsWithIncrease( 'stats', 'value', $this->statDocuments diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php index cb0f3f3827..f1e8b64d58 100644 --- a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php @@ -1044,6 +1044,8 @@ class TransactionsTest extends Scope 'required' => true, ]); + sleep(2); + // Create unique index on email $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", array_merge([ 'content-type' => 'application/json', @@ -1055,7 +1057,7 @@ class TransactionsTest extends Scope 'attributes' => ['email'], ]); - sleep(3); + sleep(2); // Create an existing document $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ From c2916c7a2dd7103f15c1babd2a0544cc291667c5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 5 Sep 2025 01:04:48 +1200 Subject: [PATCH 113/385] Lint --- .../Databases/Http/Transactions/Update.php | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index ed277e7b2f..edb0e2cbaa 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -222,8 +222,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { if ($documentId && !isset($data['$id'])) { $data['$id'] = $documentId; } @@ -247,8 +246,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -286,8 +284,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -317,8 +314,7 @@ class Update extends Action string $documentId, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -347,8 +343,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -385,8 +380,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -422,8 +416,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { $dbForProject->createDocuments( $collectionId, @@ -447,8 +440,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->updateDocuments( @@ -458,7 +450,7 @@ class Update extends Action onNext: function (Document $updated, Document $old) use (&$state, $collectionId, $createdAt) { // Check if this document was created/modified in this transaction $dependent = isset($state[$collectionId][$updated->getId()]); - + // If not in transaction state, check for timestamp conflicts if (!$dependent) { $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); @@ -466,7 +458,7 @@ class Update extends Action throw new ConflictException('Document was updated after the request timestamp'); } } - + $state[$collectionId][$updated->getId()] = $updated; } ); @@ -482,8 +474,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { // Run bulk upsert without timestamp wrapper, checking manually in callback $dbForProject->upsertDocuments( $collectionId, @@ -492,7 +483,7 @@ class Update extends Action if ($old !== null) { // This is an update - check if document was created/modified in this transaction $dependent = isset($state[$collectionId][$upserted->getId()]); - + // If not in transaction state, check for timestamp conflicts if (!$dependent) { $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); @@ -519,8 +510,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->deleteDocuments( @@ -536,7 +526,7 @@ class Update extends Action throw new ConflictException('Document was updated after the transaction operation'); } } - + // Remove from state after successful deletion if (isset($state[$collectionId][$deleted->getId()])) { unset($state[$collectionId][$deleted->getId()]); @@ -544,4 +534,4 @@ class Update extends Action } ); } -} \ No newline at end of file +} From 01586653a1a62c37123b38c1fd8f43d8a317d4c7 Mon Sep 17 00:00:00 2001 From: Veeresh <75656445+Veera-mulge@users.noreply.github.com> Date: Thu, 4 Sep 2025 20:36:03 +0530 Subject: [PATCH 114/385] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0b5c5ab4b..89390515eb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> We just announced time helper queries for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-time-helper-queries) +> We just announced inversion queries for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-inversion-queries) > Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga) From 391942ba72d5afc38407c8db936ff4d683bdef9d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 6 Sep 2025 02:37:17 +1200 Subject: [PATCH 115/385] Fix bulk logged queries --- .../Collections/Documents/Bulk/Delete.php | 4 +- .../Collections/Documents/Bulk/Update.php | 7 +- .../Transactions/TransactionsTest.php | 1620 ++++++++++++++++- 3 files changed, 1603 insertions(+), 28 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index b57c047a8c..82f940bb62 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -104,6 +104,8 @@ class Delete extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); } + $originalQueries = $queries; + try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -135,7 +137,7 @@ class Delete extends Action 'transactionInternalId' => $transaction->getSequence(), 'action' => 'bulkDelete', 'data' => [ - 'queries' => $queries, + 'queries' => $originalQueries, ], ]); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 41b8bbda74..1b9559d4b9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -116,13 +116,15 @@ class Update extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); } + $originalQueries = $queries; + try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } - if ($data['$permissions']) { + if (isset($data['$permissions'])) { $validator = new Permissions(); if (!$validator->isValid($data['$permissions'])) { throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription()); @@ -157,7 +159,7 @@ class Update extends Action 'action' => 'bulkUpdate', 'data' => [ 'data' => $data, - 'queries' => $queries, + 'queries' => $originalQueries, ], ]); @@ -167,7 +169,6 @@ class Update extends Action 'transactions', $transactionId, 'operations', - 1 ); }); diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php index f1e8b64d58..9409834be0 100644 --- a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php @@ -19,7 +19,7 @@ class TransactionsTest extends Scope /** * Test creating a transaction */ - public function testCreate(): array + public function testCreate(): void { // Create database first $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -91,21 +91,35 @@ class TransactionsTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - - return [ - 'databaseId' => $databaseId, - 'transactionId1' => $transactionId1, - 'transactionId2' => $transactionId2 - ]; } /** - * @depends testCreate + * Test adding operations to a transaction */ - public function testAddOperations(array $data): array + public function testAddOperations(): void { - $databaseId = $data['databaseId']; - $transactionId = $data['transactionId1']; + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionOperationsTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; // Create a collection for testing $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ @@ -233,20 +247,109 @@ class TransactionsTest extends Scope ]); $this->assertEquals(404, $response['headers']['status-code']); - - return array_merge($data, [ - 'collectionId' => $collectionId - ]); } /** - * @depends testAddOperations + * Test committing a transaction */ - public function testCommit(array $data): void + public function testCommit(): void { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $transactionId = $data['transactionId1']; + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionCommitTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionCommitTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add attributes + $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Add operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Test Document 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc2', + 'data' => [ + 'name' => 'Test Document 2' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Updated Document 1' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['operations']); // Commit the transaction $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ @@ -292,12 +395,32 @@ class TransactionsTest extends Scope } /** - * @depends testCreate + * Test rolling back a transaction */ - public function testRollback(array $data): void + public function testRollback(): void { - $databaseId = $data['databaseId']; - $transactionId = $data['transactionId2']; + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionRollbackTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; // Create a collection for rollback test $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ @@ -1384,4 +1507,1453 @@ class TransactionsTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // Document not found } + + /** + * Test createDocument with transactionId via normal route + */ + public function testCreateDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'WriteRoutesTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $attributes = [ + ['key' => 'name', 'type' => 'string', 'size' => 256, 'required' => true], + ['key' => 'counter', 'type' => 'integer', 'required' => false, 'min' => 0, 'max' => 10000], + ['key' => 'category', 'type' => 'string', 'size' => 256, 'required' => false], + ['key' => 'data', 'type' => 'string', 'size' => 256, 'required' => false], + ]; + + foreach ($attributes as $attr) { + $type = $attr['type']; + unset($attr['type']); + + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/{$type}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), $attr); + + $this->assertEquals(202, $response['headers']['status-code']); + } + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Create document via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_from_route', + 'data' => [ + 'name' => 'Created via normal route', + 'counter' => 100, + 'category' => 'test' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Document should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_from_route", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now exist + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_from_route", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Created via normal route', $response['body']['name']); + } + + /** + * Test updateDocument with transactionId via normal route + */ + public function testUpdateDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'UpdateRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create document outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_to_update', + 'data' => [ + 'name' => 'Original name', + 'counter' => 50, + 'category' => 'original' + ] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Update document via normal route with transactionId + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'name' => 'Updated via normal route', + 'counter' => 150, + 'category' => 'updated' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should still have original values outside transaction + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Original name', $response['body']['name']); + $this->assertEquals(50, $response['body']['counter']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now have updated values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Updated via normal route', $response['body']['name']); + $this->assertEquals(150, $response['body']['counter']); + } + + /** + * Test upsertDocument with transactionId via normal route + */ + public function testUpsertDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'UpsertRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Upsert document (create) via normal route with transactionId + $response = $this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_upsert', + 'data' => [ + 'name' => 'Created by upsert', + 'counter' => 25 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Document should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Upsert same document (update) in same transaction + $response = $this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_upsert', + 'data' => [ + 'name' => 'Updated by upsert', + 'counter' => 75 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); // Upsert in transaction returns 201 + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Updated by upsert', $response['body']['name']); + $this->assertEquals(75, $response['body']['counter']); + } + + /** + * Test deleteDocument with transactionId via normal route + */ + public function testDeleteDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DeleteRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create document outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_to_delete', + 'data' => ['name' => 'Will be deleted'] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Delete document via normal route with transactionId + $response = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'transactionId' => $transactionId + ]); + + $this->assertEquals(204, $response['headers']['status-code']); + + // Document should still exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should no longer exist + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * Test bulkCreate with transactionId via normal route + */ + public function testBulkCreate(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkCreateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk create via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documents' => [ + [ + '$id' => 'bulk_create_1', + 'name' => 'Bulk created 1', + 'category' => 'bulk_created' + ], + [ + '$id' => 'bulk_create_2', + 'name' => 'Bulk created 2', + 'category' => 'bulk_created' + ], + [ + '$id' => 'bulk_create_3', + 'name' => 'Bulk created 3', + 'category' => 'bulk_created' + ] + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); // Bulk operations return 200 + + // Documents should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_created'])->toString()] + ]); + + $this->assertEquals(0, $response['body']['total']); + + // Individual document check + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_create_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now exist + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_created'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Verify individual documents + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_create_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("Bulk created {$i}", $response['body']['name']); + $this->assertEquals('bulk_created', $response['body']['category']); + } + } + + /** + * Test bulkUpdate with transactionId via normal route + */ + public function testBulkUpdate(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create documents for bulk testing + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_update_' . $i, + 'data' => [ + 'name' => 'Bulk doc ' . $i, + 'category' => 'bulk_test' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk update via normal route with transactionId + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('category', ['bulk_test'])->toString()], + 'data' => ['category' => 'bulk_updated'], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should still have original category outside transaction + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_test'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now have updated category + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_updated'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + } + + /** + * Test bulkUpsert with transactionId via normal route + */ + public function testBulkUpsert(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpsertTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + sleep(3); + + // Create one document outside transaction + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_upsert_existing', + 'data' => [ + 'name' => 'Existing doc', + 'counter' => 10 + ] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk upsert via normal route with transactionId (updates existing, creates new) + $response = $this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documents' => [ + [ + '$id' => 'bulk_upsert_existing', + 'name' => 'Updated existing', + 'counter' => 20 + ], + [ + '$id' => 'bulk_upsert_new', + 'name' => 'New doc', + 'counter' => 30 + ] + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Original document should be unchanged, new document shouldn't exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_upsert_existing", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Existing doc', $response['body']['name']); + $this->assertEquals(10, $response['body']['counter']); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_upsert_new", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Check both documents exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_upsert_existing", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Updated existing', $response['body']['name']); + $this->assertEquals(20, $response['body']['counter']); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/bulk_upsert_new", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('New doc', $response['body']['name']); + $this->assertEquals(30, $response['body']['counter']); + } + + /** + * Test bulkDelete with transactionId via normal route + */ + public function testBulkDelete(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create documents for bulk testing + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_delete_' . $i, + 'data' => [ + 'name' => 'Delete doc ' . $i, + 'category' => 'bulk_delete_test' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk delete via normal route with transactionId + $response = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); // Bulk delete with transaction returns 200 + + // Documents should still exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now be deleted + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()] + ]); + + $this->assertEquals(0, $response['body']['total']); + } + + /** + * Test multiple single route operations in one transaction + */ + public function testMixedSingleOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'MultipleSingleRoutesDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + 'min' => 1, + 'max' => 10, + ]); + + sleep(3); + + // Create an existing document outside transaction for testing + $existingDoc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_doc', + 'data' => [ + 'name' => 'Existing Document', + 'status' => 'active', + 'priority' => 5 + ] + ]); + + $this->assertEquals(201, $existingDoc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + $this->assertEquals(201, $transaction['headers']['status-code']); + + // 1. Create new document via normal route with transactionId + $response1 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'new_doc_1', + 'data' => [ + 'name' => 'New Document 1', + 'status' => 'pending', + 'priority' => 1 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response1['headers']['status-code']); + + // 2. Create another document via normal route with transactionId + $response2 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'new_doc_2', + 'data' => [ + 'name' => 'New Document 2', + 'status' => 'pending', + 'priority' => 2 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response2['headers']['status-code']); + + // 3. Update existing document via normal route with transactionId + $response3 = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'updated', + 'priority' => 10 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response3['headers']['status-code']); + + // 4. Update the first new document (created in same transaction) + $response4 = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'active', + 'priority' => 8 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response4['headers']['status-code']); + + // 5. Delete the second new document (created in same transaction) + $response5 = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents/new_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'transactionId' => $transactionId + ]); + + $this->assertEquals(204, $response5['headers']['status-code']); + + // 6. Upsert a new document via normal route with transactionId + $response6 = $this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$collectionId}/documents/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'upserted_doc', + 'data' => [ + 'name' => 'Upserted Document', + 'status' => 'new', + 'priority' => 3 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response6['headers']['status-code']); + + // Check transaction has correct number of operations + $txnDetails = $this->client->call(Client::METHOD_GET, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $txnDetails['headers']['status-code']); + $this->assertEquals(6, $txnDetails['body']['operations']); // 6 operations total + + // Verify nothing exists outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Existing doc should still have original values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('active', $response['body']['status']); + $this->assertEquals(5, $response['body']['priority']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('committed', $response['body']['status']); + + // Verify final state after commit + // new_doc_1 should exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('New Document 1', $response['body']['name']); + $this->assertEquals('active', $response['body']['status']); + $this->assertEquals(8, $response['body']['priority']); + + // new_doc_2 should not exist (was deleted in transaction) + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/new_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // existing_doc should have updated values + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('updated', $response['body']['status']); + $this->assertEquals(10, $response['body']['priority']); + + // upserted_doc should exist + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Upserted Document', $response['body']['name']); + $this->assertEquals('new', $response['body']['status']); + $this->assertEquals(3, $response['body']['priority']); + + // Verify total document count + $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(3, $documents['body']['total']); // existing_doc, new_doc_1, upserted_doc + } + + /** + * Test mixed operations with transactions + */ + public function testMixedOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'MixedOpsTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operation via Operations\Add endpoint + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'mixed_doc1', + 'data' => ['name' => 'Via Operations Add'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['operations']); + + // Add operation via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'mixed_doc2', + 'data' => ['name' => 'Via normal route'], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Check transaction now has 2 operations + $txnDetails = $this->client->call(Client::METHOD_GET, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(2, $txnDetails['body']['operations']); + + // Both documents shouldn't exist yet + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/mixed_doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/mixed_doc2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Both documents should now exist + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/mixed_doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Via Operations Add', $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/mixed_doc2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Via normal route', $response['body']['name']); + } } From 098b0cd02838d7baa64df61f74bff2070f3717b2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 6 Sep 2025 02:37:52 +1200 Subject: [PATCH 116/385] Fix array as doc --- .../Modules/Databases/Http/Transactions/Update.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php index edb0e2cbaa..299d0f2abf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php @@ -132,6 +132,10 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; + if ($data instanceof Document) { + $data = $data->getArrayCopy(); + } + // Execute the operation based on its type switch ($action) { case 'create': @@ -197,9 +201,11 @@ class Update extends Action } if ($rollback) { - $transaction = $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'rolledBack', - ])); + $transaction = $dbForProject->updateDocument( + 'transactions', + $transactionId, + new Document(['status' => 'rolledBack']) + ); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) @@ -441,6 +447,8 @@ class Update extends Action \DateTime $createdAt, array &$state ): void { + \var_dump($data); + $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->updateDocuments( From 812a09f5564a51a3d1f86a25bdab020a5611c120 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 6 Sep 2025 04:26:49 +1200 Subject: [PATCH 117/385] Add read-your-write support --- app/init/resources.php | 5 + src/Appwrite/Databases/TransactionManager.php | 231 ++++++++++++++++++ .../Collections/Documents/Delete.php | 26 +- .../Databases/Collections/Documents/Get.php | 15 +- .../Collections/Documents/Update.php | 14 +- .../Collections/Documents/Upsert.php | 20 +- .../Databases/Collections/Documents/XList.php | 20 +- .../Transactions/TransactionsTest.php | 16 +- 8 files changed, 320 insertions(+), 27 deletions(-) create mode 100644 src/Appwrite/Databases/TransactionManager.php diff --git a/app/init/resources.php b/app/init/resources.php index e4e8fbef5e..e3955540df 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -4,6 +4,7 @@ use Ahc\Jwt\JWT; use Ahc\Jwt\JWTException; use Appwrite\Auth\Auth; use Appwrite\Auth\Key; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\Audit; use Appwrite\Event\Build; use Appwrite\Event\Certificate; @@ -1030,3 +1031,7 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef $referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : ''); return $referrer; }, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']); + +App::setResource('transactionManager', function (Database $dbForProject) { + return new TransactionManager($dbForProject); +}, ['dbForProject']); diff --git a/src/Appwrite/Databases/TransactionManager.php b/src/Appwrite/Databases/TransactionManager.php new file mode 100644 index 0000000000..533e0e489e --- /dev/null +++ b/src/Appwrite/Databases/TransactionManager.php @@ -0,0 +1,231 @@ +dbForProject = $dbForProject; + } + + /** + * Get the current state of a transaction by replaying its operations + */ + private function getTransactionState(string $transactionId): array + { + $transaction = $this->dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') { + return []; + } + + // Fetch operations ordered by sequence to replay in exact order + $operations = $this->dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc('$createdAt'), // Ensure operations are processed in order + ]); + + $state = []; + + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; + $documentId = $operation['documentId']; + $action = $operation['action']; + $data = $operation['data']; + + if ($data instanceof Document) { + $data = $data->getArrayCopy(); + } + + switch ($action) { + case 'create': + if ($documentId) { + $state[$collectionId][$documentId] = [ + 'action' => 'create', + 'document' => new Document($data), + 'exists' => true + ]; + } + break; + + case 'update': + if (isset($state[$collectionId][$documentId])) { + // Update existing document in transaction state + $existingDocument = $state[$collectionId][$documentId]['document']; + foreach ($data as $key => $value) { + if ($key !== '$id') { + $existingDocument->setAttribute($key, $value); + } + } + $state[$collectionId][$documentId]['action'] = 'update'; + } else { + // Document doesn't exist in transaction state, will be merged with committed version + $state[$collectionId][$documentId] = [ + 'action' => 'update', + 'document' => new Document($data), + 'exists' => true + ]; + } + break; + + case 'upsert': + $state[$collectionId][$documentId] = [ + 'action' => 'upsert', + 'document' => new Document($data), + 'exists' => true + ]; + break; + + case 'delete': + $state[$collectionId][$documentId] = [ + 'action' => 'delete', + 'exists' => false + ]; + break; + + case 'bulkCreate': + if (is_array($data)) { + foreach ($data as $doc) { + if ($doc instanceof Document) { + $doc = $doc->getArrayCopy(); + } + $state[$collectionId][$doc['$id']] = [ + 'action' => 'create', + 'document' => new Document($doc), + 'exists' => true + ]; + } + } + break; + } + } + + return $state; + } + + /** + * Get a document with transaction-aware logic + */ + public function getDocument( + string $collectionId, + string $documentId, + ?string $transactionId = null, + array $queries = [] + ): Document { + // If no transaction, use normal database retrieval + if ($transactionId === null) { + return $this->dbForProject->getDocument($collectionId, $documentId, $queries); + } + + $state = $this->getTransactionState($transactionId); + + + // Check if document exists in transaction state + if (isset($state[$collectionId][$documentId])) { + $docState = $state[$collectionId][$documentId]; + + if (!$docState['exists']) { + // Document was deleted in transaction + return new Document(); + } + + if ($docState['action'] === 'create') { + // Document was created in transaction, return the created version + return $docState['document']; + } + + if ($docState['action'] === 'update' || $docState['action'] === 'upsert') { + // This is an update to an existing document, merge with committed version + $committedDoc = $this->dbForProject->getDocument($collectionId, $documentId, $queries); + if (!$committedDoc->isEmpty()) { + // Apply the updates from transaction + foreach ($docState['document']->getAttributes() as $key => $value) { + if ($key !== '$id') { + $committedDoc->setAttribute($key, $value); + } + } + return $committedDoc; + } elseif ($docState['action'] === 'upsert') { + // Upsert created a new document since committed doc doesn't exist + return $docState['document']; + } + } + } + + // Document not affected by transaction, return committed version + return $this->dbForProject->getDocument($collectionId, $documentId, $queries); + } + + /** + * List documents with transaction-aware logic + */ + public function listDocuments( + string $collectionId, + ?string $transactionId = null, + array $queries = [] + ): array { + // If no transaction, use normal database retrieval + if ($transactionId === null) { + return $this->dbForProject->find($collectionId, $queries); + } + + $state = $this->getTransactionState($transactionId); + $committedDocs = $this->dbForProject->find($collectionId, $queries); + $documentMap = []; + + // Build map of committed documents + foreach ($committedDocs as $doc) { + $documentMap[$doc->getId()] = $doc; + } + + // Apply transaction state changes + if (isset($state[$collectionId])) { + foreach ($state[$collectionId] as $docId => $docState) { + if (!$docState['exists']) { + // Document was deleted, remove from results + unset($documentMap[$docId]); + } elseif ($docState['action'] === 'create') { + // Document was created, add to results + $documentMap[$docId] = $docState['document']; + } elseif ($docState['action'] === 'update' || $docState['action'] === 'upsert') { + if (isset($documentMap[$docId])) { + // Update existing document + foreach ($docState['document']->getAttributes() as $key => $value) { + if ($key !== '$id') { + $documentMap[$docId]->setAttribute($key, $value); + } + } + } elseif ($docState['action'] === 'upsert') { + // Upsert created a new document + $documentMap[$docId] = $docState['document']; + } + } + } + } + + return array_values($documentMap); + } + + /** + * Check if a document exists with transaction-aware logic + */ + public function documentExists( + string $collectionId, + string $documentId, + ?string $transactionId = null + ): bool { + $doc = $this->getDocument($collectionId, $documentId, $transactionId); + return !$doc->isEmpty(); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 7ce1d411ce..20a7b98e61 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -79,12 +80,24 @@ class Delete extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionManager') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void - { + public function action( + string $databaseId, + string $collectionId, + string $documentId, + ?string $transactionId, + ?\DateTime $requestTimestamp, + UtopiaResponse $response, + Database $dbForProject, + Event $queueForEvents, + StatsUsage $queueForStatsUsage, + TransactionManager $transactionManager, + array $plan + ): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); $isAPIKey = Auth::isAppUser(Authorization::getRoles()); @@ -101,7 +114,14 @@ class Delete extends Action } // Read permission should not be required for delete - $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); + $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + + if ($transactionId !== null) { + // Use transaction-aware document retrieval to see changes from same transaction + $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + } else { + $document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); + } if ($document->isEmpty()) { throw new Exception($this->getNotFoundException()); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php index 7f621fb33a..c69181b30d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; @@ -63,13 +64,15 @@ class Get extends Action ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('documentId', '', new UID(), 'Document ID.') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') + ->inject('transactionManager') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager): void { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -93,13 +96,17 @@ class Get extends Action try { $selects = Query::groupByType($queries)['selections'] ?? []; + $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); - if (! empty($selects)) { + // Use transaction-aware document retrieval if transactionId is provided + if ($transactionId !== null) { + $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId, $queries); + } elseif (! empty($selects)) { // has selects, allow relationship on documents! - $document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries); + $document = $dbForProject->getDocument($collectionTableId, $documentId, $queries); } else { // has no selects, disable relationship looping on documents! - $document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries)); + $document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument($collectionTableId, $documentId, $queries)); } } catch (QueryException $e) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); 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 ba3ad4d8b5..16f9dab0d7 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 @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -83,13 +84,13 @@ class Update extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionManager') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager, array $plan): void { - $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data) && \is_null($permissions)) { @@ -113,7 +114,14 @@ class Update extends Action // Read permission should not be required for update /** @var Document $document */ - $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); + $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + + if ($transactionId !== null) { + // Use transaction-aware document retrieval to see changes from same transaction + $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + } else { + $document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); + } if ($document->isEmpty()) { throw new Exception($this->getNotFoundException()); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 7a6c7e960b..da801db618 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -87,11 +88,12 @@ class Upsert extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionManager') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -124,9 +126,16 @@ class Upsert extends Action $permissions = Permission::aggregate($permissions, $allowedPermissions); + $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + // If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) if (\is_null($permissions)) { - $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); + if ($transactionId !== null) { + // Use transaction-aware document retrieval to see changes from same transaction + $oldDocument = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + } else { + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); + } if ($oldDocument->isEmpty()) { if (!empty($user->getId())) { $defaultPermissions = []; @@ -320,7 +329,12 @@ class Upsert extends Action $collectionsCache = []; if (empty($upserted[0])) { - $upserted[0] = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); + if ($transactionId !== null) { + // For transactions, get the document with transaction changes applied + $upserted[0] = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + } else { + $upserted[0] = $dbForProject->getDocument($collectionTableId, $documentId); + } } $document = $upserted[0]; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 9c8405cf18..78f91033a9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionManager; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; @@ -65,13 +66,15 @@ class XList extends Action ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') + ->inject('transactionManager') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage): void + public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager): void { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -121,17 +124,22 @@ class XList extends Action try { $selectQueries = Query::groupByType($queries)['selections'] ?? []; + $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); - if (! empty($selectQueries)) { + // Use transaction-aware document retrieval if transactionId is provided + if ($transactionId !== null) { + $documents = $transactionManager->listDocuments($collectionTableId, $transactionId, $queries); + $total = count($documents); // For transaction-aware queries, we count the actual results + } elseif (! empty($selectQueries)) { // has selects, allow relationship on documents - $documents = $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries); + $documents = $dbForProject->find($collectionTableId, $queries); + $total = $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT); } else { // has no selects, disable relationship loading on documents /* @type Document[] $documents */ - $documents = $dbForProject->skipRelationships(fn () => $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries)); + $documents = $dbForProject->skipRelationships(fn () => $dbForProject->find($collectionTableId, $queries)); + $total = $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT); } - - $total = $dbForProject->count('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries, APP_LIMIT_COUNT); } catch (OrderException $e) { $documents = $this->isCollectionsAPI() ? 'documents' : 'rows'; $attribute = $this->isCollectionsAPI() ? 'attribute' : 'column'; diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php index 9409834be0..daba81e6ec 100644 --- a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php @@ -1554,7 +1554,7 @@ class TransactionsTest extends Scope foreach ($attributes as $attr) { $type = $attr['type']; unset($attr['type']); - + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/{$type}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1874,7 +1874,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Document should now exist with updated values @@ -1982,7 +1982,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Document should no longer exist @@ -2112,7 +2112,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Documents should now exist @@ -2249,7 +2249,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Documents should now have updated category @@ -2389,7 +2389,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Check both documents exist with updated values @@ -2520,7 +2520,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Documents should now be deleted @@ -2936,7 +2936,7 @@ class TransactionsTest extends Scope ]), [ 'commit' => true ]); - + $this->assertEquals(200, $response['headers']['status-code']); // Both documents should now exist From abf6c0a0b8f3be38b5ba924862907ddebc94b0cc Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 8 Sep 2025 19:20:26 +1200 Subject: [PATCH 118/385] Add more bulk tests --- .../Transactions/TransactionsTest.php | 679 ++++++++++++++++++ 1 file changed, 679 insertions(+) diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php index daba81e6ec..e2d39d75ec 100644 --- a/tests/e2e/Services/Databases/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Transactions/TransactionsTest.php @@ -2956,4 +2956,683 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Via normal route', $response['body']['name']); } + + /** + * Test bulk update with queries that should match documents created in the same transaction + */ + public function testBulkUpdateWithTransactionAwareQueries(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkTxnAwareDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for attributes to be created + + // Create some existing documents + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'age' => 20 + $i, + 'status' => 'inactive' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Create new documents with age > 25 in transaction + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'txn_doc_1', + 'data' => [ + 'name' => 'Transaction Doc 1', + 'age' => 30, + 'status' => 'inactive' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'txn_doc_2', + 'data' => [ + 'name' => 'Transaction Doc 2', + 'age' => 35, + 'status' => 'inactive' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Step 2: Bulk update all documents with age > 25 to have status 'active' + // This should match both existing_3 (age=23 doesn't match, age=24 doesn't match, but existing documents have age 21,22,23) + // Wait, let me fix the ages - existing docs have ages 21, 22, 23, so only txn docs should match + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'active' + ], + 'queries' => [Query::greaterThan('age', 25)->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify that documents created in the transaction were updated by the bulk update + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/txn_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/txn_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); + + // Verify existing documents were not affected + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/existing_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('inactive', $response['body']['status'], "Existing document {$i} should remain inactive (age <= 25)"); + } + } + + /** + * Test bulk update with queries that should match documents updated in the same transaction + */ + public function testBulkUpdateMatchingUpdatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for attributes to be created + + // Create existing documents + for ($i = 1; $i <= 4; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_' . $i, + 'data' => [ + 'name' => 'Document ' . $i, + 'category' => 'normal', + 'priority' => 'low' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Update some documents to have category 'special' in transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'category' => 'special' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'category' => 'special' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Step 2: Bulk update all documents with category 'special' to have priority 'high' + // This should match the documents we just updated in the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'priority' => 'high' + ], + 'queries' => [Query::equal('category', ['special'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify that the updated documents were matched by bulk update + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('special', $response['body']['category']); + $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('special', $response['body']['category']); + $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); + + // Verify other documents were not affected + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_3", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('normal', $response['body']['category']); + $this->assertEquals('low', $response['body']['priority']); + } + + /** + * Test bulk delete with queries that should match documents created in the same transaction + */ + public function testBulkDeleteMatchingCreatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for attributes to be created + + // Create existing documents + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'type' => 'permanent' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Create temporary documents in transaction + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'temp_1', + 'data' => [ + 'name' => 'Temporary 1', + 'type' => 'temporary' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'temp_2', + 'data' => [ + 'name' => 'Temporary 2', + 'type' => 'temporary' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Step 2: Bulk delete all documents with type 'temporary' + // This should delete the documents we just created in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('type', ['temporary'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify temporary documents were deleted (should not exist) + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/temp_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/temp_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + + // Verify existing documents were not affected + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/existing_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code'], "Permanent document {$i} should still exist"); + $this->assertEquals('permanent', $response['body']['type']); + } + } + + /** + * Test bulk delete with queries that should match documents updated in the same transaction + */ + public function testBulkDeleteMatchingUpdatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteUpdateTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for attributes to be created + + // Create existing documents + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_' . $i, + 'data' => [ + 'name' => 'Document ' . $i, + 'status' => 'active' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Mark some documents for deletion by updating their status + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'marked_for_deletion' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_4", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'marked_for_deletion' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Step 2: Bulk delete all documents with status 'marked_for_deletion' + // This should delete the documents we just updated in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('status', ['marked_for_deletion'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify marked documents were deleted + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); + + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_4", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); + + // Verify other documents still exist + foreach ([1, 3, 5] as $i) { + $response = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/doc_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code'], "Document {$i} should still exist"); + $this->assertEquals('active', $response['body']['status']); + } + } } From 7c3ba7471062aac5dd8a76a325654669ed86f232 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 8 Sep 2025 19:21:59 +1200 Subject: [PATCH 119/385] Rename --- app/init/resources.php | 6 +++--- src/Appwrite/Databases/TransactionManager.php | 2 +- .../Http/Databases/Collections/Documents/Delete.php | 8 ++++---- .../Http/Databases/Collections/Documents/Get.php | 8 ++++---- .../Http/Databases/Collections/Documents/Update.php | 8 ++++---- .../Http/Databases/Collections/Documents/Upsert.php | 10 +++++----- .../Http/Databases/Collections/Documents/XList.php | 8 ++++---- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index e3955540df..2f158a7cdf 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -4,7 +4,7 @@ use Ahc\Jwt\JWT; use Ahc\Jwt\JWTException; use Appwrite\Auth\Auth; use Appwrite\Auth\Key; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\Audit; use Appwrite\Event\Build; use Appwrite\Event\Certificate; @@ -1032,6 +1032,6 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef return $referrer; }, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']); -App::setResource('transactionManager', function (Database $dbForProject) { - return new TransactionManager($dbForProject); +App::setResource('transactionState', function (Database $dbForProject) { + return new TransactionState($dbForProject); }, ['dbForProject']); diff --git a/src/Appwrite/Databases/TransactionManager.php b/src/Appwrite/Databases/TransactionManager.php index 533e0e489e..7d99d61d01 100644 --- a/src/Appwrite/Databases/TransactionManager.php +++ b/src/Appwrite/Databases/TransactionManager.php @@ -9,7 +9,7 @@ use Utopia\Database\Query; /** * Service for managing transaction state and providing transaction-aware document operations */ -class TransactionManager +class TransactionState { private Database $dbForProject; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 20a7b98e61..8459e06c43 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -80,7 +80,7 @@ class Delete extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('transactionManager') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } @@ -95,7 +95,7 @@ class Delete extends Action Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, - TransactionManager $transactionManager, + TransactionState $transactionState, array $plan ): void { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -118,7 +118,7 @@ class Delete extends Action if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { $document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php index c69181b30d..cced1440a5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; @@ -68,11 +68,11 @@ class Get extends Action ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') - ->inject('transactionManager') + ->inject('transactionState') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager): void + public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState): void { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -100,7 +100,7 @@ class Get extends Action // Use transaction-aware document retrieval if transactionId is provided if ($transactionId !== null) { - $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId, $queries); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId, $queries); } elseif (! empty($selects)) { // has selects, allow relationship on documents! $document = $dbForProject->getDocument($collectionTableId, $documentId, $queries); 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 16f9dab0d7..a2376b44a2 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 @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -84,12 +84,12 @@ class Update extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('transactionManager') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -118,7 +118,7 @@ class Update extends Action if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $document = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { $document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index da801db618..6690e0214f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -88,12 +88,12 @@ class Upsert extends Action ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') - ->inject('transactionManager') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager, array $plan): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -132,7 +132,7 @@ class Upsert extends Action if (\is_null($permissions)) { if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $oldDocument = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + $oldDocument = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } @@ -331,7 +331,7 @@ class Upsert extends Action if (empty($upserted[0])) { if ($transactionId !== null) { // For transactions, get the document with transaction changes applied - $upserted[0] = $transactionManager->getDocument($collectionTableId, $documentId, $transactionId); + $upserted[0] = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { $upserted[0] = $dbForProject->getDocument($collectionTableId, $documentId); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 78f91033a9..7e6931395e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents; use Appwrite\Auth\Auth; -use Appwrite\Databases\TransactionManager; +use Appwrite\Databases\TransactionState; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; @@ -70,11 +70,11 @@ class XList extends Action ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') - ->inject('transactionManager') + ->inject('transactionState') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionManager $transactionManager): void + public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState): void { $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -128,7 +128,7 @@ class XList extends Action // Use transaction-aware document retrieval if transactionId is provided if ($transactionId !== null) { - $documents = $transactionManager->listDocuments($collectionTableId, $transactionId, $queries); + $documents = $transactionState->listDocuments($collectionTableId, $transactionId, $queries); $total = count($documents); // For transaction-aware queries, we count the actual results } elseif (! empty($selectQueries)) { // has selects, allow relationship on documents From 495eeeb9296a3e00ea2714c42aab92deba16747c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 9 Sep 2025 00:50:48 +1200 Subject: [PATCH 120/385] Support legacy --- .github/workflows/tests.yml | 1 - ...actionManager.php => TransactionState.php} | 0 .../{ => Databases}/Transactions/Create.php | 2 +- .../{ => Databases}/Transactions/Delete.php | 2 +- .../Http/{ => Databases}/Transactions/Get.php | 2 +- .../Transactions/Operations/Create.php | 4 +- .../{ => Databases}/Transactions/Update.php | 4 +- .../{ => Databases}/Transactions/XList.php | 2 +- .../Http/TablesDB/Transactions/Create.php | 54 + .../Http/TablesDB/Transactions/Delete.php | 54 + .../Http/TablesDB/Transactions/Get.php | 54 + .../Transactions/Operations/Create.php | 58 + .../Http/TablesDB/Transactions/Update.php | 58 + .../Http/TablesDB/Transactions/XList.php | 54 + .../Modules/Databases/Services/Http.php | 1 - .../Databases/Services/Registry/Legacy.php | 17 + .../Databases/Services/Registry/TablesDB.php | 17 + .../Services/Registry/Transactions.php | 27 - .../Utopia/Database/Validator/Operation.php | 26 +- .../DatabasesPermissionsGuestTest.php | 2 +- .../DatabasesPermissionsMemberTest.php | 2 +- .../DatabasesPermissionsScope.php | 2 +- .../DatabasesPermissionsTeamTest.php | 2 +- .../{ => Legacy}/Transactions/ACIDTest.php | 3 +- .../Transactions/TransactionsTest.php | 2 +- .../DatabasesPermissionsGuestTest.php | 2 +- .../DatabasesPermissionsMemberTest.php | 2 +- .../DatabasesPermissionsScope.php | 2 +- .../DatabasesPermissionsTeamTest.php | 2 +- .../TablesDB/Transactions/ACIDTest.php | 625 +++ .../Transactions/TransactionsTest.php | 3638 +++++++++++++++++ 31 files changed, 4670 insertions(+), 51 deletions(-) rename src/Appwrite/Databases/{TransactionManager.php => TransactionState.php} (100%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/Create.php (97%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/Delete.php (97%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/Get.php (96%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/Operations/Create.php (97%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/Update.php (99%) rename src/Appwrite/Platform/Modules/Databases/Http/{ => Databases}/Transactions/XList.php (97%) create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php delete mode 100644 src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php rename tests/e2e/Services/Databases/Legacy/{ => Permissions}/DatabasesPermissionsGuestTest.php (99%) rename tests/e2e/Services/Databases/Legacy/{ => Permissions}/DatabasesPermissionsMemberTest.php (99%) rename tests/e2e/Services/Databases/{TablesDB => Legacy/Permissions}/DatabasesPermissionsScope.php (97%) rename tests/e2e/Services/Databases/Legacy/{ => Permissions}/DatabasesPermissionsTeamTest.php (99%) rename tests/e2e/Services/Databases/{ => Legacy}/Transactions/ACIDTest.php (99%) rename tests/e2e/Services/Databases/{ => Legacy}/Transactions/TransactionsTest.php (99%) rename tests/e2e/Services/Databases/TablesDB/{ => Permissions}/DatabasesPermissionsGuestTest.php (99%) rename tests/e2e/Services/Databases/TablesDB/{ => Permissions}/DatabasesPermissionsMemberTest.php (99%) rename tests/e2e/Services/Databases/{Legacy => TablesDB/Permissions}/DatabasesPermissionsScope.php (97%) rename tests/e2e/Services/Databases/TablesDB/{ => Permissions}/DatabasesPermissionsTeamTest.php (99%) create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2ca0c58db8..7b1a243da1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -169,7 +169,6 @@ jobs: Console, Databases/Legacy, Databases/TablesDB, - Databases/Transactions, Functions, FunctionsSchedule, GraphQL, diff --git a/src/Appwrite/Databases/TransactionManager.php b/src/Appwrite/Databases/TransactionState.php similarity index 100% rename from src/Appwrite/Databases/TransactionManager.php rename to src/Appwrite/Databases/TransactionState.php diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php similarity index 97% rename from src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php rename to src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index 2c98565568..d2f114a888 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -1,6 +1,6 @@ param('transactionId', '', new UID(), 'Transaction ID.') - ->param('operations', [], new ArrayList(new Operation()), 'Array of staged operations.', true) + ->param('operations', [], new ArrayList(new Operation(type: 'legacy')), 'Array of staged operations.', true) ->inject('response') ->inject('dbForProject') ->inject('plan') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php similarity index 99% rename from src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php rename to src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 299d0f2abf..e854306a47 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -1,6 +1,6 @@ setHttpMethod(self::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/tablesdb/transactions') + ->desc('Create transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'createTransaction', + description: '/docs/references/tablesdb/create-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_CREATED, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php new file mode 100644 index 0000000000..9440b586c5 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php @@ -0,0 +1,54 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/tablesdb/transactions/:transactionId') + ->desc('Delete transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'deleteTransaction', + description: '/docs/references/tablesdb/delete-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_NOCONTENT, + model: UtopiaResponse::MODEL_NONE, + ) + ], + contentType: ContentType::NONE + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php new file mode 100644 index 0000000000..a5e9c374a6 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php @@ -0,0 +1,54 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/tablesdb/transactions/:transactionId') + ->desc('Get transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'getTransaction', + description: '/docs/references/tablesdb/get-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php new file mode 100644 index 0000000000..eac524682c --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -0,0 +1,58 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/tablesdb/transactions/:transactionId/operations') + ->desc('Add operations to transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'createOperations', + description: '/docs/references/tablesdb/create-operations.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_CREATED, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('operations', [], new ArrayList(new Operation(type: 'tablesdb')), 'Array of staged operations.', true) + ->inject('response') + ->inject('dbForProject') + ->inject('plan') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php new file mode 100644 index 0000000000..d5b0e737a0 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -0,0 +1,58 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/tablesdb/transactions/:transactionId') + ->desc('Update transaction') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'updateTransaction', + description: '/docs/references/tablesdb/update-transaction.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION, + ) + ], + contentType: ContentType::JSON + )) + ->param('transactionId', '', new UID(), 'Transaction ID.') + ->param('commit', false, new Boolean(), 'Commit transaction?', true) + ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) + ->inject('response') + ->inject('dbForProject') + ->inject('queueForDeletes') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php new file mode 100644 index 0000000000..e04d28f200 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php @@ -0,0 +1,54 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/tablesdb/transactions') + ->desc('List transactions') + ->groups(['api', 'database', 'transactions']) + ->label('scope', 'transactions.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'tablesDB', + group: 'transactions', + name: 'listTransactions', + description: '/docs/references/tablesdb/list-transactions.md', + auth: [AuthType::KEY], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_TRANSACTION_LIST, + ) + ], + contentType: ContentType::JSON + )) + ->param('queries', [], new Transactions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries).', true) + ->inject('response') + ->inject('dbForProject') + ->callback($this->action(...)); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Http.php b/src/Appwrite/Platform/Modules/Databases/Services/Http.php index e5a2d92b40..86f48c42bb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Http.php @@ -19,7 +19,6 @@ class Http extends Service foreach ([ LegacyRegistry::class, TablesDBRegistry::class, - TransactionsRegistry::class, ] as $registrar) { new $registrar($this); } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php index 9db5e00f8f..3286aecf93 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php @@ -58,6 +58,12 @@ use Appwrite\Platform\Modules\Databases\Http\Databases\Create as CreateDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Delete as DeleteDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Get as GetDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Logs\XList as ListDatabaseLogs; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Create as CreateTransaction; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Delete as DeleteTransaction; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Get as GetTransaction; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Operations\Create as CreateOperations; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Update as UpdateTransaction; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\XList as ListTransactions; use Appwrite\Platform\Modules\Databases\Http\Databases\Update as UpdateDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\Get as GetDatabaseUsage; use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\XList as ListDatabaseUsage; @@ -82,6 +88,7 @@ class Legacy extends Base $this->registerDocumentActions($service); $this->registerAttributeActions($service); $this->registerIndexActions($service); + $this->registerTransactionActions($service); } public function registerDatabaseActions(Service $service): void @@ -191,4 +198,14 @@ class Legacy extends Base $service->addAction(DeleteIndex::getName(), new DeleteIndex()); $service->addAction(ListIndexes::getName(), new ListIndexes()); } + + private function registerTransactionActions(Service $service): void + { + $service->addAction(CreateTransaction::getName(), new CreateTransaction()); + $service->addAction(GetTransaction::getName(), new GetTransaction()); + $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); + $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); + $service->addAction(ListTransactions::getName(), new ListTransactions()); + $service->addAction(CreateOperations::getName(), new CreateOperations()); + } } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php index 11be613629..4a02ac684e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/TablesDB.php @@ -57,6 +57,12 @@ use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Rows\XList as ListR use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Update as UpdateTable; use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Usage\Get as GetTableUsage; use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\XList as ListTables; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\Create as CreateTransaction; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\Delete as DeleteTransaction; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\Get as GetTransaction; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\Operations\Create as CreateOperations; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\Update as UpdateTransaction; +use Appwrite\Platform\Modules\Databases\Http\TablesDB\Transactions\XList as ListTransactions; use Appwrite\Platform\Modules\Databases\Http\TablesDB\Update as UpdateTablesDatabase; use Appwrite\Platform\Modules\Databases\Http\TablesDB\Usage\Get as GetTablesDatabaseUsage; use Appwrite\Platform\Modules\Databases\Http\TablesDB\Usage\XList as ListTablesDatabaseUsage; @@ -81,6 +87,7 @@ class TablesDB extends Base $this->registerColumnActions($service); $this->registerIndexActions($service); $this->registerRowActions($service); + $this->registerTransactionActions($service); } private function registerDatabaseActions(Service $service): void @@ -188,4 +195,14 @@ class TablesDB extends Base $service->addAction(IncrementRowColumn::getName(), new IncrementRowColumn()); $service->addAction(DecrementRowColumn::getName(), new DecrementRowColumn()); } + + private function registerTransactionActions(Service $service): void + { + $service->addAction(CreateTransaction::getName(), new CreateTransaction()); + $service->addAction(GetTransaction::getName(), new GetTransaction()); + $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); + $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); + $service->addAction(ListTransactions::getName(), new ListTransactions()); + $service->addAction(CreateOperations::getName(), new CreateOperations()); + } } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php deleted file mode 100644 index b41d6adcdb..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Transactions.php +++ /dev/null @@ -1,27 +0,0 @@ -addAction(CreateTransaction::getName(), new CreateTransaction()); - $service->addAction(GetTransaction::getName(), new GetTransaction()); - $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); - $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); - $service->addAction(ListTransactions::getName(), new ListTransactions()); - $service->addAction(CreateOperations::getName(), new CreateOperations()); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 594648db3e..ec2d4f0d49 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -11,7 +11,6 @@ class Operation extends Validator /** @var array */ private array $required = [ 'databaseId', - 'collectionId', 'action', ]; @@ -39,6 +38,27 @@ class Operation extends Validator 'bulkDelete' => true, ]; + private string $collectionIdName = ''; + private string $documentIdName = ''; + + public function __construct(private readonly string $type) + { + switch ($this->type) { + case 'legacy': + $this->collectionIdName = 'collectionId'; + $this->documentIdName = 'documentId'; + break; + case 'tablesdb': + $this->collectionIdName = 'tableId'; + $this->documentIdName = 'rowId'; + break; + default: + throw new \InvalidArgumentException('Invalid type provided.'); + } + + $this->required[] = $this->collectionIdName; + } + public function getDescription(): string { return $this->description; @@ -85,9 +105,9 @@ class Operation extends Validator // If action requires documentId, it must be present if ( isset($this->requiresDocumentId[$value['action']]) && - !\array_key_exists('documentId', $value) + !\array_key_exists($this->documentIdName, $value) ) { - $this->description = "Key 'documentId' is required for action '{$value['action']}'"; + $this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'"; return false; } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesPermissionsGuestTest.php b/tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php similarity index 99% rename from tests/e2e/Services/Databases/Legacy/DatabasesPermissionsGuestTest.php rename to tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php index abeef6c222..6496aa285a 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesPermissionsGuestTest.php +++ b/tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php @@ -1,6 +1,6 @@ client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'AtomicityTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection with unique constraint + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'AtomicityTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add unique column + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); + + // Add unique index + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'unique_email', + 'type' => Database::INDEX_UNIQUE, + 'columns' => ['email'] + ]); + + sleep(3); + + // Create first document outside transaction + $doc1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'existing@example.com' + ] + ]); + + $this->assertEquals(201, $doc1['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed. Response: ' . json_encode($transaction)); + $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id. Response body: ' . json_encode($transaction['body'])); + $transactionId = $transaction['body']['$id']; + + // Add operations - second one will fail due to unique constraint + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'newuser@example.com' // This should succeed + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'existing@example.com' // This will fail - duplicate + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'email' => 'anotheruser@example.com' // This should not be created due to atomicity + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code'], 'Add operations failed. Response: ' . json_encode($response['body'])); + + // Attempt to commit - should fail due to unique constraint violation + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + if ($response['headers']['status-code'] === 200) { + // If transaction succeeded, all documents should be created + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have 4 documents total (1 original + 3 from transaction) + // But since we have a unique constraint violation, this might fail + $this->assertGreaterThanOrEqual(1, $documents['body']['total']); + } else { + $this->assertEquals(409, $response['headers']['status-code']); // Conflict error + + // Verify NO new documents were created (atomicity) + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(1, $documents['body']['total']); // Only the original document + $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + } + } + + /** + * Test consistency - schema validation and constraints + */ + public function testConsistency(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ConsistencyTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection with required fields and constraints + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'ConsistencyTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add required string column + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'required_field', + 'size' => 256, + 'required' => true, + ]); + + // Add integer column with min/max constraints + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'required' => true, + 'min' => 18, + 'max' => 100 + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operations with both valid and invalid data + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Valid User', + 'age' => 25 // Valid age + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Too Young User', + 'age' => 10 // Below minimum - will fail constraint + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => [ + 'required_field' => 'Another Valid User', + 'age' => 30 // Valid but should not be created due to transaction failure + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Attempt to commit - should fail due to constraint violation + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertContains($response['headers']['status-code'], [400, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body'])); + + // Verify no documents were created + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(0, $documents['body']['total']); + } + + /** + * Test isolation - concurrent transactions on same data + */ + public function testIsolation(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IsolationTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'IsolationTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add counter column + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000 + ]); + + sleep(2); + + // Create initial document with counter + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'shared_counter', + 'data' => [ + 'counter' => 0 + ] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create first transaction + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction1['headers']['status-code'], 'Transaction 1 creation should succeed'); + $this->assertArrayHasKey('$id', $transaction1['body'], 'Transaction 1 response should have $id'); + $transactionId1 = $transaction1['body']['$id']; + + // Create second transaction + $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction2['headers']['status-code'], 'Transaction 2 creation should succeed'); + $this->assertArrayHasKey('$id', $transaction2['body'], 'Transaction 2 response should have $id'); + $transactionId2 = $transaction2['body']['$id']; + + // Transaction 1: Increment counter by 10 + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId1}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'shared_counter', + 'action' => 'increment', + 'data' => [ + 'column' => 'counter', + 'value' => 10 + ] + ] + ] + ]); + + // Transaction 2: Increment counter by 5 + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'shared_counter', + 'action' => 'increment', + 'data' => [ + 'column' => 'counter', + 'value' => 5 + ] + ] + ] + ]); + + // Commit first transaction + $response1 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId1}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response1['headers']['status-code']); + + // Commit second transaction + $response2 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response2['headers']['status-code']); + + // Check final value - both increments should be applied + $document = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/shared_counter", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Both increments should be applied: 0 + 10 + 5 = 15 + $this->assertEquals(15, $document['body']['counter']); + } + + /** + * Test durability - committed data persists + */ + public function testDurability(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DurabilityTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'DurabilityTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add column + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create and commit transaction with multiple operations + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed'); + $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id'); + $transactionId = $transaction['body']['$id']; + + // Add multiple operations + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'durable_doc_1', + 'data' => [ + 'data' => 'Important data 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'durable_doc_2', + 'data' => [ + 'data' => 'Important data 2' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'durable_doc_1', + 'data' => [ + 'data' => 'Updated important data 1' + ] + ] + ] + ]); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body'])); + $this->assertEquals('committed', $response['body']['status']); + + // List all documents to see what was created + $allDocs = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertGreaterThan(0, $allDocs['body']['total'], 'Should have created documents. Found: ' . json_encode($allDocs['body'])); + + // Verify documents exist and have correct data + $document1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $document1['headers']['status-code']); + $this->assertEquals('Updated important data 1', $document1['body']['data']); + + $document2 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $document2['headers']['status-code']); + $this->assertEquals('Important data 2', $document2['body']['data']); + + // Further update outside transaction to ensure persistence + $update = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'data' => 'Modified outside transaction' + ] + ]); + + $this->assertEquals(200, $update['headers']['status-code']); + + // Verify the update persisted + $document1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Modified outside transaction', $document1['body']['data']); + + // List all documents to verify total count + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(2, $documents['body']['total']); + } +} diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php new file mode 100644 index 0000000000..7d19a65648 --- /dev/null +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -0,0 +1,3638 @@ +client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Test creating a transaction with default TTL + $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertArrayHasKey('$id', $response['body']); + $this->assertArrayHasKey('status', $response['body']); + $this->assertArrayHasKey('operations', $response['body']); + $this->assertArrayHasKey('expiresAt', $response['body']); + $this->assertEquals('pending', $response['body']['status']); + $this->assertEquals(0, $response['body']['operations']); + + $transactionId1 = $response['body']['$id']; + + // Test creating a transaction with custom TTL + $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 900 + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals('pending', $response['body']['status']); + + $expiresAt = new \DateTime($response['body']['expiresAt']); + $now = new \DateTime(); + $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); + $this->assertGreaterThan(800, $diff); + $this->assertLessThan(1000, $diff); + + $transactionId2 = $response['body']['$id']; + + // Test invalid TTL values + $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 30 // Below minimum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 4000 // Above maximum + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test adding operations to a transaction + */ + public function testAddOperations(): void + { + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionOperationsTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Create a collection for testing + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionOperationsTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add columns + $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $column['headers']['status-code']); + + // Wait for column to be created + sleep(2); + + // Add valid operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Test Document 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc2', + 'data' => [ + 'name' => 'Test Document 2' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['operations']); + + // Test adding more operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Updated Document 1' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['operations']); + + // Test invalid database ID + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => 'invalid_database', + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); + + // Test invalid collection ID + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => 'invalid_collection', + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * Test committing a transaction + */ + public function testCommit(): void + { + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionCommitTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionCommitTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add columns + $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $column['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Add operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Test Document 1' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc2', + 'data' => [ + 'name' => 'Test Document 2' + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'doc1', + 'data' => [ + 'name' => 'Updated Document 1' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['operations']); + + // Commit the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('committed', $response['body']['status']); + + // Verify documents were created + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(2, $documents['body']['total']); + + // Verify the update was applied + $doc1Found = false; + foreach ($documents['body']['documents'] as $doc) { + if ($doc['$id'] === 'doc1') { + $this->assertEquals('Updated Document 1', $doc['name']); + $doc1Found = true; + } + } + $this->assertTrue($doc1Found, 'Document doc1 should exist with updated name'); + + // Test committing already committed transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test rolling back a transaction + */ + public function testRollback(): void + { + // Create database first + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'TransactionRollbackTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Create a collection for rollback test + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TransactionRollbackTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add column + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Add operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'rollback_doc', + 'data' => [ + 'value' => 'Should not exist' + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Rollback the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('rolledBack', $response['body']['status']); + + // Verify no documents were created + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(0, $documents['body']['total']); + } + + /** + * Test transaction expiration + */ + public function testTransactionExpiration(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ExpirationTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction with minimum TTL (60 seconds) + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'ttl' => 60 + ]); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Add operation + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['data' => 'Should expire'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Verify transaction was created with correct expiration + $txnDetails = $this->client->call(Client::METHOD_GET, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $txnDetails['headers']['status-code']); + $this->assertEquals('pending', $txnDetails['body']['status']); + + // Verify expiration time is approximately 60 seconds from now + $expiresAt = new \DateTime($txnDetails['body']['expiresAt']); + $now = new \DateTime(); + $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); + $this->assertGreaterThan(55, $diff); + $this->assertLessThan(65, $diff); + } + + /** + * Test maximum operations per transaction + */ + public function testTransactionSizeLimit(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'SizeLimitTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [Permission::create(Role::any())], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Try to add operations exceeding the limit (assuming limit is 100) + // We'll add 50 operations twice to test incremental limit + $operations = []; + for ($i = 0; $i < 50; $i++) { + $operations[] = [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc_' . $i, + 'data' => ['value' => 'Test ' . $i] + ]; + } + + // First batch should succeed + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => $operations + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(50, $response['body']['operations']); + + // Second batch of 50 more operations + $operations = []; + for ($i = 50; $i < 100; $i++) { + $operations[] = [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => 'doc_' . $i, + 'action' => 'create', + 'data' => ['value' => 'Test ' . $i] + ]; + } + + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => $operations + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(100, $response['body']['operations']); + + // Try to add one more operation - should fail + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'doc_overflow', + 'data' => ['value' => 'This should fail'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test concurrent transactions with conflicting operations + */ + public function testConcurrentTransactionConflicts(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ConflictTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000, + ]); + + sleep(2); + + // Create initial document + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'shared_doc', + 'data' => ['counter' => 100] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create two transactions + $txn1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $txn2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId1 = $txn1['body']['$id']; + $transactionId2 = $txn2['body']['$id']; + + // Both transactions try to update the same document + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId1}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'shared_doc', + 'data' => ['counter' => 200] + ] + ] + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'shared_doc', + 'data' => ['counter' => 300] + ] + ] + ]); + + // Commit first transaction + $response1 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId1}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response1['headers']['status-code']); + + // Commit second transaction - should fail with conflict + $response2 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response2['headers']['status-code']); // Conflict + + // Verify the document has the value from first transaction + $doc = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/shared_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $doc['body']['counter']); + } + + /** + * Test deleting a document that's being updated in a transaction + */ + public function testDeleteDocumentDuringTransaction(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DeleteConflictDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create document + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'target_doc', + 'data' => ['data' => 'Original'] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add update operation to transaction + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'target_doc', + 'data' => ['data' => 'Updated in transaction'] + ] + ] + ]); + + // Delete the document outside of transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/target_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(204, $response['headers']['status-code']); + + // Try to commit transaction - should fail because document no longer exists + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response['headers']['status-code']); // Conflict + } + + /** + * Test bulk operations in transactions + */ + public function testBulkOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkOpsDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); + + // Create some initial documents + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'category' => 'old' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + // Bulk create + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkCreate', + 'data' => [ + ['$id' => 'bulk_1', 'name' => 'Bulk 1', 'category' => 'new'], + ['$id' => 'bulk_2', 'name' => 'Bulk 2', 'category' => 'new'], + ['$id' => 'bulk_3', 'name' => 'Bulk 3', 'category' => 'new'], + ] + ], + // Bulk update + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [Query::equal('category', ['old'])->toString()], + 'data' => ['category' => 'updated'] + ] + ], + // Bulk delete + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [Query::equal('name', ['Existing 5'])->toString()] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify results + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have 7 documents (5 existing - 1 deleted + 3 new) + $this->assertEquals(7, $documents['body']['total']); + + // Check categories were updated + $oldCategoryCount = 0; + $updatedCategoryCount = 0; + $newCategoryCount = 0; + + foreach ($documents['body']['documents'] as $doc) { + switch ($doc['category']) { + case 'old': + $oldCategoryCount++; + break; + case 'updated': + $updatedCategoryCount++; + break; + case 'new': + $newCategoryCount++; + break; + } + } + + $this->assertEquals(0, $oldCategoryCount); + $this->assertEquals(4, $updatedCategoryCount); // 4 existing docs updated + $this->assertEquals(3, $newCategoryCount); // 3 new docs + } + + /** + * Test transaction with mixed success and failure operations + */ + public function testPartialFailureRollback(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'PartialFailureDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns with constraints + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create unique index on email + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/indexes", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'unique_email', + 'type' => 'unique', + 'columns' => ['email'], + ]); + + sleep(2); + + // Create an existing document + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => ID::unique(), + 'data' => ['email' => 'existing@example.com'] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operations - mix of valid and invalid + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid1@example.com'] // Valid + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid2@example.com'] // Valid + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'existing@example.com'] // Will fail - duplicate + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['email' => 'valid3@example.com'] // Would be valid but should rollback + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Try to commit - should fail and rollback all operations + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(409, $response['headers']['status-code']); // Conflict due to duplicate + + // Verify NO new documents were created (atomicity) + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(1, $documents['body']['total']); // Only the original document + $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + } + + /** + * Test double commit/rollback attempts + */ + public function testDoubleCommitRollback(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DoubleCommitDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [Permission::create(Role::any())], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Test double commit + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operation + $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => ID::unique(), + 'data' => ['data' => 'Test'] + ] + ] + ]); + + // First commit + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Second commit attempt - should fail + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already committed + + // Test double rollback + $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId2 = $transaction2['body']['$id']; + + // First rollback + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Second rollback attempt - should fail + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rollback' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already rolled back + } + + /** + * Test operations on non-existent documents + */ + public function testOperationsOnNonExistentDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'NonExistentDocDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Try to update non-existent document + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'update', + 'documentId' => 'non_existent_doc', + 'data' => ['data' => 'Should fail'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); // Operation added + + // Commit should fail + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); // Document not found + + // Test delete non-existent document + $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId2 = $transaction2['body']['$id']; + + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId2}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'delete', + 'documentId' => 'non_existent_doc', + 'data' => [] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit should fail + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); // Document not found + } + + /** + * Test createDocument with transactionId via normal route + */ + public function testCreateDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'WriteRoutesTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $columns = [ + ['key' => 'name', 'type' => 'string', 'size' => 256, 'required' => true], + ['key' => 'counter', 'type' => 'integer', 'required' => false, 'min' => 0, 'max' => 10000], + ['key' => 'category', 'type' => 'string', 'size' => 256, 'required' => false], + ['key' => 'data', 'type' => 'string', 'size' => 256, 'required' => false], + ]; + + foreach ($columns as $attr) { + $type = $attr['type']; + unset($attr['type']); + + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/{$type}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), $attr); + + $this->assertEquals(202, $response['headers']['status-code']); + } + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Create document via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_from_route', + 'data' => [ + 'name' => 'Created via normal route', + 'counter' => 100, + 'category' => 'test' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Document should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_from_route", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_from_route", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Created via normal route', $response['body']['name']); + } + + /** + * Test updateDocument with transactionId via normal route + */ + public function testUpdateDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'UpdateRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create document outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_to_update', + 'data' => [ + 'name' => 'Original name', + 'counter' => 50, + 'category' => 'original' + ] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Update document via normal route with transactionId + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'name' => 'Updated via normal route', + 'counter' => 150, + 'category' => 'updated' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should still have original values outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Original name', $response['body']['name']); + $this->assertEquals(50, $response['body']['counter']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now have updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Updated via normal route', $response['body']['name']); + $this->assertEquals(150, $response['body']['counter']); + } + + /** + * Test upsertDocument with transactionId via normal route + */ + public function testUpsertDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'UpsertRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Upsert document (create) via normal route with transactionId + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_upsert', + 'data' => [ + 'name' => 'Created by upsert', + 'counter' => 25 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Document should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Upsert same document (update) in same transaction + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_upsert', + 'data' => [ + 'name' => 'Updated by upsert', + 'counter' => 75 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); // Upsert in transaction returns 201 + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should now exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Updated by upsert', $response['body']['name']); + $this->assertEquals(75, $response['body']['counter']); + } + + /** + * Test deleteDocument with transactionId via normal route + */ + public function testDeleteDocument(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'DeleteRouteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create document outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_to_delete', + 'data' => ['name' => 'Will be deleted'] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Delete document via normal route with transactionId + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'transactionId' => $transactionId + ]); + + $this->assertEquals(204, $response['headers']['status-code']); + + // Document should still exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Document should no longer exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * Test bulkCreate with transactionId via normal route + */ + public function testBulkCreate(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkCreateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk create via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documents' => [ + [ + '$id' => 'bulk_create_1', + 'name' => 'Bulk created 1', + 'category' => 'bulk_created' + ], + [ + '$id' => 'bulk_create_2', + 'name' => 'Bulk created 2', + 'category' => 'bulk_created' + ], + [ + '$id' => 'bulk_create_3', + 'name' => 'Bulk created 3', + 'category' => 'bulk_created' + ] + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); // Bulk operations return 200 + + // Documents should not exist outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_created'])->toString()] + ]); + + $this->assertEquals(0, $response['body']['total']); + + // Individual document check + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_create_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_created'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Verify individual documents + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_create_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("Bulk created {$i}", $response['body']['name']); + $this->assertEquals('bulk_created', $response['body']['category']); + } + } + + /** + * Test bulkUpdate with transactionId via normal route + */ + public function testBulkUpdate(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create documents for bulk testing + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_update_' . $i, + 'data' => [ + 'name' => 'Bulk doc ' . $i, + 'category' => 'bulk_test' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk update via normal route with transactionId + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('category', ['bulk_test'])->toString()], + 'data' => ['category' => 'bulk_updated'], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should still have original category outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_test'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now have updated category + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_updated'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + } + + /** + * Test bulkUpsert with transactionId via normal route + */ + public function testBulkUpsert(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpsertTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + sleep(3); + + // Create one document outside transaction + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_upsert_existing', + 'data' => [ + 'name' => 'Existing doc', + 'counter' => 10 + ] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk upsert via normal route with transactionId (updates existing, creates new) + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documents' => [ + [ + '$id' => 'bulk_upsert_existing', + 'name' => 'Updated existing', + 'counter' => 20 + ], + [ + '$id' => 'bulk_upsert_new', + 'name' => 'New doc', + 'counter' => 30 + ] + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Original document should be unchanged, new document shouldn't exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_existing", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Existing doc', $response['body']['name']); + $this->assertEquals(10, $response['body']['counter']); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_new", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Check both documents exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_existing", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('Updated existing', $response['body']['name']); + $this->assertEquals(20, $response['body']['counter']); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_new", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('New doc', $response['body']['name']); + $this->assertEquals(30, $response['body']['counter']); + } + + /** + * Test bulkDelete with transactionId via normal route + */ + public function testBulkDelete(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + // Create documents for bulk testing + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'bulk_delete_' . $i, + 'data' => [ + 'name' => 'Delete doc ' . $i, + 'category' => 'bulk_delete_test' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Bulk delete via normal route with transactionId + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); // Bulk delete with transaction returns 200 + + // Documents should still exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()] + ]); + + $this->assertEquals(3, $response['body']['total']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Documents should now be deleted + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::equal('category', ['bulk_delete_test'])->toString()] + ]); + + $this->assertEquals(0, $response['body']['total']); + } + + /** + * Test multiple single route operations in one transaction + */ + public function testMixedSingleOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'MultipleSingleRoutesDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + 'min' => 1, + 'max' => 10, + ]); + + sleep(3); + + // Create an existing document outside transaction for testing + $existingDoc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_doc', + 'data' => [ + 'name' => 'Existing Document', + 'status' => 'active', + 'priority' => 5 + ] + ]); + + $this->assertEquals(201, $existingDoc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + $this->assertEquals(201, $transaction['headers']['status-code']); + + // 1. Create new document via normal route with transactionId + $response1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'new_doc_1', + 'data' => [ + 'name' => 'New Document 1', + 'status' => 'pending', + 'priority' => 1 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response1['headers']['status-code']); + + // 2. Create another document via normal route with transactionId + $response2 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'new_doc_2', + 'data' => [ + 'name' => 'New Document 2', + 'status' => 'pending', + 'priority' => 2 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response2['headers']['status-code']); + + // 3. Update existing document via normal route with transactionId + $response3 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'updated', + 'priority' => 10 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response3['headers']['status-code']); + + // 4. Update the first new document (created in same transaction) + $response4 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'active', + 'priority' => 8 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response4['headers']['status-code']); + + // 5. Delete the second new document (created in same transaction) + $response5 = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'transactionId' => $transactionId + ]); + + $this->assertEquals(204, $response5['headers']['status-code']); + + // 6. Upsert a new document via normal route with transactionId + $response6 = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'upserted_doc', + 'data' => [ + 'name' => 'Upserted Document', + 'status' => 'new', + 'priority' => 3 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response6['headers']['status-code']); + + // Check transaction has correct number of operations + $txnDetails = $this->client->call(Client::METHOD_GET, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $txnDetails['headers']['status-code']); + $this->assertEquals(6, $txnDetails['body']['operations']); // 6 operations total + + // Verify nothing exists outside transaction yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Existing doc should still have original values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('active', $response['body']['status']); + $this->assertEquals(5, $response['body']['priority']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('committed', $response['body']['status']); + + // Verify final state after commit + // new_doc_1 should exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('New Document 1', $response['body']['name']); + $this->assertEquals('active', $response['body']['status']); + $this->assertEquals(8, $response['body']['priority']); + + // new_doc_2 should not exist (was deleted in transaction) + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // existing_doc should have updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals('updated', $response['body']['status']); + $this->assertEquals(10, $response['body']['priority']); + + // upserted_doc should exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Upserted Document', $response['body']['name']); + $this->assertEquals('new', $response['body']['status']); + $this->assertEquals(3, $response['body']['priority']); + + // Verify total document count + $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(3, $documents['body']['total']); // existing_doc, new_doc_1, upserted_doc + } + + /** + * Test mixed operations with transactions + */ + public function testMixedOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'MixedOpsTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add operation via Operations\Add endpoint + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'create', + 'documentId' => 'mixed_doc1', + 'data' => ['name' => 'Via Operations Add'] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['operations']); + + // Add operation via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'mixed_doc2', + 'data' => ['name' => 'Via normal route'], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Check transaction now has 2 operations + $txnDetails = $this->client->call(Client::METHOD_GET, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(2, $txnDetails['body']['operations']); + + // Both documents shouldn't exist yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Both documents should now exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Via Operations Add', $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Via normal route', $response['body']['name']); + } + + /** + * Test bulk update with queries that should match documents created in the same transaction + */ + public function testBulkUpdateWithTransactionAwareQueries(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkTxnAwareDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for columns to be created + + // Create some existing documents + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'age' => 20 + $i, + 'status' => 'inactive' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Create new documents with age > 25 in transaction + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'txn_doc_1', + 'data' => [ + 'name' => 'Transaction Doc 1', + 'age' => 30, + 'status' => 'inactive' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'txn_doc_2', + 'data' => [ + 'name' => 'Transaction Doc 2', + 'age' => 35, + 'status' => 'inactive' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Step 2: Bulk update all documents with age > 25 to have status 'active' + // This should match both existing_3 (age=23 doesn't match, age=24 doesn't match, but existing documents have age 21,22,23) + // Wait, let me fix the ages - existing docs have ages 21, 22, 23, so only txn docs should match + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'active' + ], + 'queries' => [Query::greaterThan('age', 25)->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify that documents created in the transaction were updated by the bulk update + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/txn_doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/txn_doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); + + // Verify existing documents were not affected + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('inactive', $response['body']['status'], "Existing document {$i} should remain inactive (age <= 25)"); + } + } + + /** + * Test bulk update with queries that should match documents updated in the same transaction + */ + public function testBulkUpdateMatchingUpdatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for columns to be created + + // Create existing documents + for ($i = 1; $i <= 4; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_' . $i, + 'data' => [ + 'name' => 'Document ' . $i, + 'category' => 'normal', + 'priority' => 'low' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Update some documents to have category 'special' in transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'category' => 'special' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'category' => 'special' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Step 2: Bulk update all documents with category 'special' to have priority 'high' + // This should match the documents we just updated in the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'priority' => 'high' + ], + 'queries' => [Query::equal('category', ['special'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify that the updated documents were matched by bulk update + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('special', $response['body']['category']); + $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('special', $response['body']['category']); + $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); + + // Verify other documents were not affected + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_3", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('normal', $response['body']['category']); + $this->assertEquals('low', $response['body']['priority']); + } + + /** + * Test bulk delete with queries that should match documents created in the same transaction + */ + public function testBulkDeleteMatchingCreatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for columns to be created + + // Create existing documents + for ($i = 1; $i <= 3; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'existing_' . $i, + 'data' => [ + 'name' => 'Existing ' . $i, + 'type' => 'permanent' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Create temporary documents in transaction + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'temp_1', + 'data' => [ + 'name' => 'Temporary 1', + 'type' => 'temporary' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'temp_2', + 'data' => [ + 'name' => 'Temporary 2', + 'type' => 'temporary' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Step 2: Bulk delete all documents with type 'temporary' + // This should delete the documents we just created in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('type', ['temporary'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify temporary documents were deleted (should not exist) + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/temp_1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/temp_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + + // Verify existing documents were not affected + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code'], "Permanent document {$i} should still exist"); + $this->assertEquals('permanent', $response['body']['type']); + } + } + + /** + * Test bulk delete with queries that should match documents updated in the same transaction + */ + public function testBulkDeleteMatchingUpdatedDocuments(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteUpdateTxnDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + sleep(3); // Wait for columns to be created + + // Create existing documents + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => 'doc_' . $i, + 'data' => [ + 'name' => 'Document ' . $i, + 'status' => 'active' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Step 1: Mark some documents for deletion by updating their status + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'marked_for_deletion' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_4", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'status' => 'marked_for_deletion' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Step 2: Bulk delete all documents with status 'marked_for_deletion' + // This should delete the documents we just updated in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'queries' => [Query::equal('status', ['marked_for_deletion'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify marked documents were deleted + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); + + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_4", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); + + // Verify other documents still exist + foreach ([1, 3, 5] as $i) { + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_{$i}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code'], "Document {$i} should still exist"); + $this->assertEquals('active', $response['body']['status']); + } + } +} From 4478edc211bbbf8540ee83472611c9badc0b7ff5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 04:25:21 +0000 Subject: [PATCH 121/385] fix linter --- app/config/console.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/config/console.php b/app/config/console.php index 2ee0712691..6ba9533728 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -4,8 +4,6 @@ * Initializes console project document. */ -use Appwrite\Network\Validator\Origin; -use Appwrite\Auth\Auth; use Appwrite\Network\Platform; use Utopia\Database\Helpers\ID; use Utopia\System\System; From 631c00023e418d7e99414bbdcd7ca6ead686ec42 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 04:38:14 +0000 Subject: [PATCH 122/385] update lock --- composer.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.lock b/composer.lock index d2cb0af89f..70c79d2f52 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": "7553e976312b0423cc31544abb91caec", + "content-hash": "6e00e41bd4002c54cae87f68e5cb3742", "packages": [ { "name": "adhocore/jwt", @@ -3693,16 +3693,16 @@ }, { "name": "utopia-php/database", - "version": "1.4.1", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b5ea4d133a1a4e747b7522e61e072289129a06f4" + "reference": "16f96e5d9784dae87d4f6b864e87da8e3be15507" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b5ea4d133a1a4e747b7522e61e072289129a06f4", - "reference": "b5ea4d133a1a4e747b7522e61e072289129a06f4", + "url": "https://api.github.com/repos/utopia-php/database/zipball/16f96e5d9784dae87d4f6b864e87da8e3be15507", + "reference": "16f96e5d9784dae87d4f6b864e87da8e3be15507", "shasum": "" }, "require": { @@ -3743,9 +3743,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.4.1" + "source": "https://github.com/utopia-php/database/tree/1.4.4" }, - "time": "2025-09-05T13:23:52+00:00" + "time": "2025-09-10T00:50:05+00:00" }, { "name": "utopia-php/detector", @@ -5062,16 +5062,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.3.2", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a" + "reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/375a6c9b168db6fdf58fbe0d49d2261d80700b4a", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac", + "reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac", "shasum": "" }, "require": { @@ -5107,9 +5107,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.3.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.3.4" }, - "time": "2025-09-05T15:50:35+00:00" + "time": "2025-09-08T11:56:04+00:00" }, { "name": "doctrine/annotations", @@ -8567,7 +8567,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8591,5 +8591,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From 41dbe1a384ee8762d69c261226a7d45a2b8034c7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 07:03:11 +0000 Subject: [PATCH 123/385] Fix internal ID refactor --- app/controllers/api/account.php | 16 ++++++++-------- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index fb36829e2c..5d39c2728c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1110,7 +1110,7 @@ App::post('/v1/account/sessions/anonymous') [ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'provider' => SESSION_PROVIDER_ANONYMOUS, 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -1726,7 +1726,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_OAUTH2, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, @@ -2039,7 +2039,7 @@ App::post('/v1/account/tokens/magic-url') $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_MAGIC_URL, 'secret' => $proofForToken->hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, @@ -2312,7 +2312,7 @@ App::post('/v1/account/tokens/email') $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_EMAIL, 'secret' => $proofForCode->hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, @@ -2652,7 +2652,7 @@ App::post('/v1/account/tokens/phone') $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_PHONE, 'secret' => $proofForToken->hash($secret), 'expire' => $expire, @@ -3354,7 +3354,7 @@ App::post('/v1/account/recovery') $recovery = new Document([ '$id' => ID::unique(), 'userId' => $profile->getId(), - 'userInternalId' => $profile->getInternalId(), + 'userInternalId' => $profile->getSequence(), 'type' => TOKEN_TYPE_RECOVERY, 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, @@ -3617,7 +3617,7 @@ App::post('/v1/account/verification') $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_VERIFICATION, 'secret' => $proofForToken->hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => $expire, @@ -3869,7 +3869,7 @@ App::post('/v1/account/verification/phone') $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_PHONE, 'secret' => $proofForCode->hash($secret), 'expire' => $expire, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 0f2672af79..4732c6c810 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1257,7 +1257,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Permission::delete(Role::user($user->getId())), ], 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'provider' => SESSION_PROVIDER_EMAIL, 'providerUid' => $user->getAttribute('email'), 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index dbd33f0903..95cf41ee09 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2239,7 +2239,7 @@ App::post('/v1/users/:userId/sessions') [ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'provider' => SESSION_PROVIDER_SERVER, 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -2327,7 +2327,7 @@ App::post('/v1/users/:userId/tokens') $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), + 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_GENERIC, 'secret' => $proofForToken->hash($secret), 'expire' => $expire, From 487663f8922ee3fe1cef4002d59cfd1a878a26fa Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 07:30:22 +0000 Subject: [PATCH 124/385] fixed attribute --- app/config/collections/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/collections/common.php b/app/config/collections/common.php index 2dd9a763e1..a291f7b19d 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1115,7 +1115,7 @@ return [ '$id' => ID::custom('expire'), 'type' => Database::VAR_DATETIME, 'size' => 0, - 'required' => true, + 'required' => false, 'format' => '', 'signed' => false, 'default' => null, From 6099313cb42dd66df7aac8a94fc90a560a0b5b26 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 07:57:45 +0000 Subject: [PATCH 125/385] Fxi teams tests --- app/controllers/api/account.php | 8 ++++++-- tests/e2e/Services/Teams/TeamsBaseClient.php | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 5d39c2728c..aae55c52e3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2954,8 +2954,9 @@ App::patch('/v1/account/password') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') + ->inject('store') ->inject('proofForPassword') - ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword) { + ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword) { $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); // Check old password only if its an existing user. @@ -2995,7 +2996,10 @@ App::patch('/v1/account/password') ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()); $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, Auth::$secret); + + $proofsToken = new ProofsToken(); + + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofsToken); $invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false; if ($invalidate && !empty($current)) { foreach ($sessions as $session) { diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 5c8e94feb1..74ffe704b5 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -718,7 +718,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(4, $response['body']['total']); + $this->assertEquals(3, $response['body']['total']); $ownerMembershipUid = $response['body']['memberships'][0]['$id']; @@ -773,7 +773,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['total']); + $this->assertEquals(2, $response['body']['total']); /** * Test for when the owner tries to delete their membership From 4578819a582c75b3e78e99e21a2a0fd4a0dbdd81 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 07:59:00 +0000 Subject: [PATCH 126/385] revert test update --- tests/e2e/Services/Teams/TeamsBaseClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 74ffe704b5..5c8e94feb1 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -718,7 +718,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['total']); + $this->assertEquals(4, $response['body']['total']); $ownerMembershipUid = $response['body']['memberships'][0]['$id']; @@ -773,7 +773,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(2, $response['body']['total']); + $this->assertEquals(3, $response['body']['total']); /** * Test for when the owner tries to delete their membership From 356fbed325a521d72191b570620adea9fd60c7f6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 09:09:12 +0000 Subject: [PATCH 127/385] fix password update --- app/controllers/api/account.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index aae55c52e3..eafff19073 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2956,8 +2956,9 @@ App::patch('/v1/account/password') ->inject('hooks') ->inject('store') ->inject('proofForPassword') - ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword) { - + ->inject('proofForToken') + ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + var_dump('updating password ' . $oldPassword . ':' . $password); $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password @@ -2997,9 +2998,8 @@ App::patch('/v1/account/password') $sessions = $user->getAttribute('sessions', []); - $proofsToken = new ProofsToken(); + $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofsToken); $invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false; if ($invalidate && !empty($current)) { foreach ($sessions as $session) { From 28a2b577e74af4c945ef26f80080c0b987c822df Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 16:36:09 +1200 Subject: [PATCH 128/385] Update lock --- composer.json | 2 +- composer.lock | 72 +++++++++---------- .../Modules/Databases/Services/Http.php | 1 - 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 9042cb088f..1dc7288441 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "2.*", + "utopia-php/database": "1.*", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index e134913475..f82ba2aced 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": "3565fcc2471b5d18a159b6da1c8fad31", + "content-hash": "7553e976312b0423cc31544abb91caec", "packages": [ { "name": "adhocore/jwt", @@ -3296,16 +3296,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.0.1", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a" + "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a", - "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/c5e2232033b507a07f72180dc56d37e1872ee7be", + "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be", "shasum": "" }, "require": { @@ -3313,7 +3313,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "2.*" + "utopia-php/database": "1.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3341,9 +3341,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.0.1" + "source": "https://github.com/utopia-php/abuse/tree/1.0.0" }, - "time": "2025-09-04T12:46:54+00:00" + "time": "2025-08-13T09:12:54+00:00" }, { "name": "utopia-php/analytics", @@ -3393,21 +3393,21 @@ }, { "name": "utopia-php/audit", - "version": "1.0.1", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22" + "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", - "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/c0ed75f4d068f1f6c2e7149a909490d4214e72bb", + "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "2.*" + "utopia-php/database": "1.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3434,9 +3434,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/1.0.1" + "source": "https://github.com/utopia-php/audit/tree/1.0.0" }, - "time": "2025-09-04T12:46:43+00:00" + "time": "2025-08-13T09:09:00+00:00" }, { "name": "utopia-php/cache", @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "2.0.0", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" + "reference": "1bac5c66567cd0718b4c133c8550b3c6076ff908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1bac5c66567cd0718b4c133c8550b3c6076ff908", + "reference": "1bac5c66567cd0718b4c133c8550b3c6076ff908", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.0.0" + "source": "https://github.com/utopia-php/database/tree/1.4.5" }, - "time": "2025-09-04T12:36:53+00:00" + "time": "2025-09-11T03:10:29+00:00" }, { "name": "utopia-php/detector", @@ -4190,16 +4190,16 @@ }, { "name": "utopia-php/migration", - "version": "1.0.2", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "eb60a61934be1d6f2f4fdabd9903a841ba078bc9" + "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/eb60a61934be1d6f2f4fdabd9903a841ba078bc9", - "reference": "eb60a61934be1d6f2f4fdabd9903a841ba078bc9", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/c42935a6a4ee3701c68d24244e82ecb39e945ec4", + "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4", "shasum": "" }, "require": { @@ -4207,7 +4207,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "2.*", + "utopia-php/database": "1.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4240,9 +4240,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.0.2" + "source": "https://github.com/utopia-php/migration/tree/1.1.1" }, - "time": "2025-09-04T12:47:05+00:00" + "time": "2025-09-10T06:17:20+00:00" }, { "name": "utopia-php/orchestration", @@ -5007,16 +5007,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.3.2", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a" + "reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/375a6c9b168db6fdf58fbe0d49d2261d80700b4a", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac", + "reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac", "shasum": "" }, "require": { @@ -5052,9 +5052,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.3.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.3.4" }, - "time": "2025-09-05T15:50:35+00:00" + "time": "2025-09-08T11:56:04+00:00" }, { "name": "doctrine/annotations", @@ -8512,7 +8512,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8536,5 +8536,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Http.php b/src/Appwrite/Platform/Modules/Databases/Services/Http.php index 86f48c42bb..f683f537bc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Http.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Databases\Services; use Appwrite\Platform\Modules\Databases\Http\Init\Timeout; use Appwrite\Platform\Modules\Databases\Services\Registry\Legacy as LegacyRegistry; use Appwrite\Platform\Modules\Databases\Services\Registry\TablesDB as TablesDBRegistry; -use Appwrite\Platform\Modules\Databases\Services\Registry\Transactions as TransactionsRegistry; use Utopia\Platform\Service; class Http extends Service From f9b92ddead46751765a7f8316aafdc60425da9df Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 16:37:43 +1200 Subject: [PATCH 129/385] Fix namespace --- tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php | 2 +- .../Databases/TablesDB/Transactions/TransactionsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php index 90282568e4..89e095cb53 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php @@ -1,6 +1,6 @@ Date: Thu, 11 Sep 2025 17:54:55 +1200 Subject: [PATCH 130/385] Legacy DB support --- composer.json | 2 +- composer.lock | 56 +- .../Http/Databases/Collections/Action.php | 4 +- .../Collections/Attributes/Action.php | 4 +- .../Collections/Attributes/Boolean/Create.php | 4 +- .../Collections/Attributes/Boolean/Update.php | 4 +- .../Attributes/Datetime/Create.php | 4 +- .../Attributes/Datetime/Update.php | 4 +- .../Collections/Attributes/Delete.php | 4 +- .../Collections/Attributes/Email/Create.php | 4 +- .../Collections/Attributes/Email/Update.php | 4 +- .../Collections/Attributes/Enum/Create.php | 4 +- .../Collections/Attributes/Enum/Update.php | 4 +- .../Collections/Attributes/Float/Create.php | 4 +- .../Collections/Attributes/Float/Update.php | 4 +- .../Databases/Collections/Attributes/Get.php | 4 +- .../Collections/Attributes/IP/Create.php | 4 +- .../Collections/Attributes/IP/Update.php | 4 +- .../Collections/Attributes/Integer/Create.php | 4 +- .../Collections/Attributes/Integer/Update.php | 4 +- .../Collections/Attributes/Line/Create.php | 4 +- .../Collections/Attributes/Line/Update.php | 4 +- .../Collections/Attributes/Point/Create.php | 4 +- .../Collections/Attributes/Point/Update.php | 4 +- .../Collections/Attributes/Polygon/Create.php | 4 +- .../Collections/Attributes/Polygon/Update.php | 4 +- .../Attributes/Relationship/Create.php | 4 +- .../Attributes/Relationship/Update.php | 4 +- .../Collections/Attributes/String/Create.php | 6 +- .../Collections/Attributes/String/Update.php | 4 +- .../Collections/Attributes/URL/Create.php | 4 +- .../Collections/Attributes/URL/Update.php | 4 +- .../Collections/Attributes/XList.php | 6 +- .../Http/Databases/Collections/Create.php | 2 +- .../Http/Databases/Collections/Delete.php | 2 +- .../Collections/Documents/Action.php | 4 +- .../Documents/Attribute/Decrement.php | 8 +- .../Documents/Attribute/Increment.php | 8 +- .../Collections/Documents/Bulk/Delete.php | 10 +- .../Collections/Documents/Bulk/Update.php | 10 +- .../Collections/Documents/Bulk/Upsert.php | 10 +- .../Collections/Documents/Create.php | 20 +- .../Collections/Documents/Delete.php | 4 +- .../Databases/Collections/Documents/Get.php | 4 +- .../Collections/Documents/Logs/XList.php | 2 +- .../Collections/Documents/Update.php | 4 +- .../Collections/Documents/Upsert.php | 4 +- .../Databases/Collections/Documents/XList.php | 6 +- .../Http/Databases/Collections/Get.php | 2 +- .../Databases/Collections/Indexes/Action.php | 4 +- .../Databases/Collections/Indexes/Create.php | 4 +- .../Databases/Collections/Indexes/Delete.php | 4 +- .../Databases/Collections/Indexes/Get.php | 4 +- .../Databases/Collections/Indexes/XList.php | 4 +- .../Http/Databases/Collections/Logs/XList.php | 2 +- .../Http/Databases/Collections/Update.php | 2 +- .../Http/Databases/Collections/XList.php | 4 +- .../Http/Databases/Transactions/Action.php | 67 ++ .../Http/Databases/Transactions/Create.php | 3 +- .../Http/Databases/Transactions/Delete.php | 3 +- .../Http/Databases/Transactions/Get.php | 3 +- .../Transactions/Operations/Create.php | 10 +- .../Http/Databases/Transactions/Update.php | 16 +- .../Http/Databases/Transactions/XList.php | 3 +- .../Tables/Columns/Boolean/Create.php | 4 +- .../Tables/Columns/Boolean/Update.php | 4 +- .../Tables/Columns/Datetime/Create.php | 4 +- .../Tables/Columns/Datetime/Update.php | 4 +- .../Http/TablesDB/Tables/Columns/Delete.php | 4 +- .../TablesDB/Tables/Columns/Email/Create.php | 4 +- .../TablesDB/Tables/Columns/Email/Update.php | 4 +- .../TablesDB/Tables/Columns/Enum/Create.php | 4 +- .../TablesDB/Tables/Columns/Enum/Update.php | 4 +- .../TablesDB/Tables/Columns/Float/Create.php | 4 +- .../TablesDB/Tables/Columns/Float/Update.php | 4 +- .../Http/TablesDB/Tables/Columns/Get.php | 4 +- .../TablesDB/Tables/Columns/IP/Create.php | 4 +- .../TablesDB/Tables/Columns/IP/Update.php | 4 +- .../Tables/Columns/Integer/Create.php | 4 +- .../Tables/Columns/Integer/Update.php | 4 +- .../TablesDB/Tables/Columns/Line/Create.php | 4 +- .../TablesDB/Tables/Columns/Line/Update.php | 4 +- .../TablesDB/Tables/Columns/Point/Create.php | 4 +- .../TablesDB/Tables/Columns/Point/Update.php | 4 +- .../Tables/Columns/Polygon/Create.php | 4 +- .../Tables/Columns/Polygon/Update.php | 4 +- .../Tables/Columns/Relationship/Create.php | 4 +- .../Tables/Columns/Relationship/Update.php | 4 +- .../TablesDB/Tables/Columns/String/Create.php | 4 +- .../TablesDB/Tables/Columns/String/Update.php | 4 +- .../TablesDB/Tables/Columns/URL/Create.php | 4 +- .../TablesDB/Tables/Columns/URL/Update.php | 4 +- .../Http/TablesDB/Tables/Columns/XList.php | 4 +- .../Databases/Http/TablesDB/Tables/Create.php | 2 +- .../Databases/Http/TablesDB/Tables/Delete.php | 2 +- .../Databases/Http/TablesDB/Tables/Get.php | 2 +- .../Http/TablesDB/Tables/Indexes/Create.php | 4 +- .../Http/TablesDB/Tables/Indexes/Delete.php | 4 +- .../Http/TablesDB/Tables/Indexes/Get.php | 4 +- .../Http/TablesDB/Tables/Indexes/XList.php | 4 +- .../Http/TablesDB/Tables/Logs/XList.php | 4 +- .../Http/TablesDB/Tables/Rows/Bulk/Delete.php | 4 +- .../Http/TablesDB/Tables/Rows/Bulk/Update.php | 4 +- .../Http/TablesDB/Tables/Rows/Bulk/Upsert.php | 4 +- .../TablesDB/Tables/Rows/Column/Decrement.php | 4 +- .../TablesDB/Tables/Rows/Column/Increment.php | 4 +- .../Http/TablesDB/Tables/Rows/Create.php | 8 +- .../Http/TablesDB/Tables/Rows/Delete.php | 5 +- .../Http/TablesDB/Tables/Rows/Get.php | 6 +- .../Http/TablesDB/Tables/Rows/Logs/XList.php | 2 +- .../Http/TablesDB/Tables/Rows/Update.php | 5 +- .../Http/TablesDB/Tables/Rows/Upsert.php | 5 +- .../Http/TablesDB/Tables/Rows/XList.php | 6 +- .../Databases/Http/TablesDB/Tables/Update.php | 2 +- .../Http/TablesDB/Tables/Usage/Get.php | 2 +- .../Databases/Http/TablesDB/Tables/XList.php | 2 +- .../Databases/Services/Registry/Legacy.php | 1 + .../TablesDB/Transactions/ACIDTest.php | 166 ++-- .../Transactions/TransactionsTest.php | 802 +++++++++--------- 119 files changed, 831 insertions(+), 762 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php diff --git a/composer.json b/composer.json index 1dc7288441..9042cb088f 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "1.*", + "utopia-php/database": "2.*", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index f82ba2aced..a1c8862168 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": "7553e976312b0423cc31544abb91caec", + "content-hash": "773efb29b9b584b1249790e0016c823a", "packages": [ { "name": "adhocore/jwt", @@ -3296,16 +3296,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be" + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/c5e2232033b507a07f72180dc56d37e1872ee7be", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a", + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a", "shasum": "" }, "require": { @@ -3313,7 +3313,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3341,9 +3341,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.0.0" + "source": "https://github.com/utopia-php/abuse/tree/1.0.1" }, - "time": "2025-08-13T09:12:54+00:00" + "time": "2025-09-04T12:46:54+00:00" }, { "name": "utopia-php/analytics", @@ -3393,21 +3393,21 @@ }, { "name": "utopia-php/audit", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb" + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/c0ed75f4d068f1f6c2e7149a909490d4214e72bb", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3434,9 +3434,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/1.0.0" + "source": "https://github.com/utopia-php/audit/tree/1.0.1" }, - "time": "2025-08-13T09:09:00+00:00" + "time": "2025-09-04T12:46:43+00:00" }, { "name": "utopia-php/cache", @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "1.4.5", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1bac5c66567cd0718b4c133c8550b3c6076ff908" + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1bac5c66567cd0718b4c133c8550b3c6076ff908", - "reference": "1bac5c66567cd0718b4c133c8550b3c6076ff908", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.4.5" + "source": "https://github.com/utopia-php/database/tree/2.0.0" }, - "time": "2025-09-11T03:10:29+00:00" + "time": "2025-09-04T12:36:53+00:00" }, { "name": "utopia-php/detector", @@ -4190,16 +4190,16 @@ }, { "name": "utopia-php/migration", - "version": "1.1.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4" + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/c42935a6a4ee3701c68d24244e82ecb39e945ec4", - "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/6fb6f8f032cd34c3c65728a55d494adeac2ff038", + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038", "shasum": "" }, "require": { @@ -4207,7 +4207,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "1.*", + "utopia-php/database": "2.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4240,9 +4240,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.1.1" + "source": "https://github.com/utopia-php/migration/tree/1.1.0" }, - "time": "2025-09-10T06:17:20+00:00" + "time": "2025-09-10T05:45:30+00:00" }, { "name": "utopia-php/orchestration", diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php index 0939376c49..b22119fad1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php @@ -53,7 +53,7 @@ abstract class Action extends UtopiaAction /** * Get the SDK group name for the current action. */ - protected function getSdkGroup(): string + protected function getSDKGroup(): string { return $this->isCollectionsAPI() ? 'collections' : 'tables'; } @@ -61,7 +61,7 @@ abstract class Action extends UtopiaAction /** * Get the SDK namespace for the current action. */ - protected function getSdkNamespace(): string + protected function getSDKNamespace(): string { return $this->isCollectionsAPI() ? 'databases' : 'tablesDB'; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php index f3903d91a7..ae97d1b7cf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php @@ -66,7 +66,7 @@ abstract class Action extends UtopiaAction * * Can be used for XList operations as well! */ - protected function getSdkGroup(): string + protected function getSDKGroup(): string { return $this->isCollectionsAPI() ? 'attributes' : 'columns'; } @@ -74,7 +74,7 @@ abstract class Action extends UtopiaAction /** * Get the SDK namespace for the current action. */ - protected function getSdkNamespace(): string + protected function getSDKNamespace(): string { return $this->isCollectionsAPI() ? 'databases' : 'tablesDB'; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php index 6c4be252a0..6c8e5dcf3d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php @@ -42,8 +42,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-boolean-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php index 752e8c6c59..d4724ea551 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php @@ -42,8 +42,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-boolean-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Create.php index 5d5b8fe4bf..1f2098e7af 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Create.php @@ -43,8 +43,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-datetime-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php index e1d1d40f75..cb4d0d924b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-datetime-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php index 8dec7530bf..eb51044323 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php @@ -43,8 +43,8 @@ class Delete extends Action ->label('audits.event', 'attribute.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/delete-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php index bc58ca10af..cbfd66e4d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php @@ -43,8 +43,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-email-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php index 53300725d7..2446722f7a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-email-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php index cd22e87d1c..98ed83861d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php @@ -45,8 +45,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-enum-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php index 951e43effe..23dc807360 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php @@ -44,8 +44,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-enum-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php index 2a8253c22c..83deb92edc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php @@ -45,8 +45,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-float-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php index 327a766cee..7f295a1a94 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-float-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Get.php index 28ee584778..91fa3582f7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Get.php @@ -47,8 +47,8 @@ class Get extends Action ->label('scope', 'collections.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/get-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php index aa699b4a49..6e6264466c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php @@ -43,8 +43,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-ip-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php index f61cf21732..6cedf10760 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-ip-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php index 81a11b0471..090d63c403 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php @@ -45,8 +45,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-integer-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php index 11cfb24943..b6ae79bd8a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-integer-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Create.php index 4eb3345823..f691fc29cf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Create.php @@ -44,8 +44,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-line-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php index 8fcb867923..52f04ba5bc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-line-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Create.php index 47c88497ba..aae715ba1e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Create.php @@ -44,8 +44,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-point-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php index bffe802927..73964ab461 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-point-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Create.php index 6cc74254ae..6fbbd46d2c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Create.php @@ -44,8 +44,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-polygon-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php index d78bfa8ef0..23eb06f45d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-polygon-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php index a062816329..75471b826c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php @@ -45,8 +45,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-relationship-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php index c19c4ff046..897cbd434f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php @@ -41,8 +41,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-relationship-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php index 592aa8ee93..1527c4d1d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php @@ -47,8 +47,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-string-attribute.md', auth: [AuthType::KEY], @@ -95,7 +95,7 @@ class Create extends Action array $plan ): void { if (!App::isDevelopment() && $encrypt && !empty($plan) && !($plan['databasesAllowEncrypt'] ?? false)) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Encrypted string ' . $this->getSdkGroup() . ' are not available on your plan. Please upgrade to create encrypted string ' . $this->getSdkGroup() . '.'); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Encrypted string ' . $this->getSDKGroup() . ' are not available on your plan. Please upgrade to create encrypted string ' . $this->getSDKGroup() . '.'); } if ($encrypt && $size < APP_DATABASE_ENCRYPT_SIZE_MIN) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php index 03efac1430..8614dfb202 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php @@ -45,8 +45,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-string-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php index 39153b3cb8..ce0175966b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php @@ -43,8 +43,8 @@ class Create extends Action ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-url-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php index 7ace4d0683..7ba12ad98a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php @@ -43,8 +43,8 @@ class Update extends Action ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-url-attribute.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/XList.php index 1852290f76..6daa180df9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/XList.php @@ -41,8 +41,8 @@ class XList extends Action ->label('scope', 'collections.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/list-attributes.md', auth: [AuthType::KEY], @@ -141,7 +141,7 @@ class XList extends Action $response->dynamic(new Document([ 'total' => $total, - $this->getSdkGroup() => $attributes, + $this->getSDKGroup() => $attributes, ]), $this->getResponseModel()); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php index e06dae89bb..b810ce602f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php @@ -52,7 +52,7 @@ class Create extends Action ->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}') ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-collection.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php index d20570fb43..d124a47289 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php @@ -42,7 +42,7 @@ class Delete extends Action ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/delete-collection.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index d1d0738990..871d1c4cbb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -76,7 +76,7 @@ abstract class Action extends AppwriteAction * * Can be used for XList operations as well! */ - protected function getSdkGroup(): string + protected function getSDKGroup(): string { return $this->isCollectionsAPI() ? 'documents' : 'rows'; } @@ -84,7 +84,7 @@ abstract class Action extends AppwriteAction /** * Get the SDK namespace for the current action. */ - protected function getSdkNamespace(): string + protected function getSDKNamespace(): string { return $this->isCollectionsAPI() ? 'databases' : 'tablesDB'; } 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 39bb5b1f97..cbe0ddceaf 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 @@ -54,8 +54,8 @@ class Decrement extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/decrement-document-attribute.md', auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], @@ -166,9 +166,9 @@ class Decrement extends Action } catch (NotFoundException) { throw new Exception($this->getStructureNotFoundException()); } catch (LimitException) { - throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the minimum value of ' . $min); + 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'); + 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()); } 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 0fa19f8370..22e19c69a5 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 @@ -54,8 +54,8 @@ class Increment extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/increment-document-attribute.md', auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], @@ -166,9 +166,9 @@ class Increment extends Action } catch (NotFoundException) { throw new Exception($this->getStructureNotFoundException()); } catch (LimitException) { - throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the maximum value of ' . $max); + 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'); + 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()); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 82f940bb62..57e81c1f3c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -51,8 +51,8 @@ class Delete extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/delete-documents.md', auth: [AuthType::ADMIN, AuthType::KEY], @@ -101,7 +101,7 @@ class Delete extends Action ); if ($hasRelationships) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for ' . $this->getSDKNamespace() . ' with relationship attributes'); } $originalQueries = $queries; @@ -152,7 +152,7 @@ class Delete extends Action // Return successful response without actually deleting documents $response->dynamic(new Document([ - $this->getSdkGroup() => [], + $this->getSDKGroup() => [], 'total' => 0, // Can't predict how many would be deleted ]), $this->getResponseModel()); return; @@ -187,7 +187,7 @@ class Delete extends Action $response->dynamic(new Document([ 'total' => $modified, - $this->getSdkGroup() => $documents, + $this->getSDKGroup() => $documents, ]), $this->getResponseModel()); $this->triggerBulk( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 1b9559d4b9..c2d1bb06fc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -54,8 +54,8 @@ class Update extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-documents.md', auth: [AuthType::ADMIN, AuthType::KEY], @@ -113,7 +113,7 @@ class Update extends Action ); if ($hasRelationships) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for ' . $this->getSDKNamespace() . ' with relationship attributes'); } $originalQueries = $queries; @@ -174,7 +174,7 @@ class Update extends Action // Return successful response without actually updating documents $response->dynamic(new Document([ - $this->getSdkGroup() => [], + $this->getSDKGroup() => [], 'total' => 0, // Can't predict how many would be updated ]), $this->getResponseModel()); return; @@ -214,7 +214,7 @@ class Update extends Action $response->dynamic(new Document([ 'total' => $modified, - $this->getSdkGroup() => $documents + $this->getSDKGroup() => $documents ]), $this->getResponseModel()); $this->triggerBulk( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 618d640d95..371e0c7360 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -52,8 +52,8 @@ class Upsert extends Action ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/upsert-documents.md', auth: [AuthType::ADMIN, AuthType::KEY], @@ -103,7 +103,7 @@ class Upsert extends Action ); if ($hasRelationships) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes'); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSDKNamespace() . ' with relationship attributes'); } foreach ($documents as $key => $document) { @@ -150,7 +150,7 @@ class Upsert extends Action // Return successful response without actually upserting documents $response->dynamic(new Document([ - $this->getSdkGroup() => [], + $this->getSDKGroup() => [], 'total' => \count($documents), ]), $this->getResponseModel()); @@ -192,7 +192,7 @@ class Upsert extends Action $response->dynamic(new Document([ 'total' => $modified, - $this->getSdkGroup() => $upserted + $this->getSDKGroup() => $upserted ]), $this->getResponseModel()); $this->triggerBulk( 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 afc5036aa3..7fd29ba6ef 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 @@ -63,8 +63,8 @@ class Create extends Action ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), desc: 'Create document', description: '/docs/references/databases/create-document.md', @@ -90,8 +90,8 @@ class Create extends Action ), ), new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: $this->getBulkActionName(self::getName()), desc: 'Create documents', description: '/docs/references/databases/create-documents.md', @@ -148,7 +148,7 @@ class Create extends Action } if (!empty($data) && !empty($documents)) { // Both single and bulk documents provided - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, ' . $this->getSdkGroup()); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, ' . $this->getSDKGroup()); } if (!empty($data) && empty($documentId)) { // Single document provided without document ID @@ -161,12 +161,12 @@ class Create extends Action $documentId = $this->isCollectionsAPI() ? 'documentId' : 'rowId'; throw new Exception( Exception::GENERAL_BAD_REQUEST, - "Param \"$documentId\" is not allowed when creating multiple " . $this->getSdkGroup() . ', set "$id" on each instead.' + "Param \"$documentId\" is not allowed when creating multiple " . $this->getSDKGroup() . ', set "$id" on each instead.' ); } if (!empty($documents) && !empty($permissions)) { // Bulk documents provided with permissions - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple ' . $this->getSdkGroup() . ', set "$permissions" on each instead'); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple ' . $this->getSDKGroup() . ', set "$permissions" on each instead'); } $isBulk = true; @@ -200,7 +200,7 @@ class Create extends Action ); if ($isBulk && $hasRelationships) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for ' . $this->getSdkNamespace() .' with relationship ' . $this->getStructureContext()); + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for ' . $this->getSDKNamespace() .' with relationship ' . $this->getStructureContext()); } $setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) { @@ -407,7 +407,7 @@ class Create extends Action // Return successful response without actually creating documents if ($isBulk) { $response->dynamic(new Document([ - $this->getSdkGroup() => [], + $this->getSDKGroup() => [], 'total' => \count($documents), ]), $this->getBulkResponseModel()); } else { @@ -468,7 +468,7 @@ class Create extends Action if ($isBulk) { $response->dynamic(new Document([ 'total' => count($documents), - $this->getSdkGroup() => $documents + $this->getSDKGroup() => $documents ]), $this->getBulkResponseModel()); $this->triggerBulk( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 8459e06c43..be73068c06 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -54,8 +54,8 @@ class Delete extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/delete-document.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php index cced1440a5..3d8cd9198c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php @@ -43,8 +43,8 @@ class Get extends Action ->label('scope', 'documents.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/get-document.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php index 93c8639520..ac7602ff4c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php @@ -48,7 +48,7 @@ class XList extends Action ->label('scope', 'documents.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'logs', name: self::getName(), description: '/docs/references/databases/get-document-logs.md', 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 a2376b44a2..d144ab9194 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 @@ -56,8 +56,8 @@ class Update extends Action ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-document.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 6690e0214f..f93aea88c8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -58,8 +58,8 @@ class Upsert extends Action ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/upsert-document.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 7e6931395e..d2f90c0951 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -46,8 +46,8 @@ class XList extends Action ->label('scope', 'documents.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/list-documents.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -206,7 +206,7 @@ class XList extends Action $response->dynamic(new Document([ 'total' => $total, // rows or documents - $this->getSdkGroup() => $documents, + $this->getSDKGroup() => $documents, ]), $this->getResponseModel()); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Get.php index af4b6bf733..89739570c7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Get.php @@ -37,7 +37,7 @@ class Get extends Action ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/get-collection.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Action.php index f136fdd29b..400d716e41 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Action.php @@ -52,7 +52,7 @@ abstract class Action extends UtopiaAction /** * Get the SDK group name for the current action. */ - final protected function getSdkGroup(): string + final protected function getSDKGroup(): string { return 'indexes'; } @@ -60,7 +60,7 @@ abstract class Action extends UtopiaAction /** * Get the SDK namespace for the current action. */ - final protected function getSdkNamespace(): string + final protected function getSDKNamespace(): string { return $this->isCollectionsAPI() ? 'databases' : 'tablesDB'; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php index 733259f091..0c6ef8bb23 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php @@ -51,8 +51,8 @@ class Create extends Action ->label('audits.event', 'index.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/create-index.md', auth: [AuthType::KEY], 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 9ba4c98046..2bccfdfb52 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 @@ -46,8 +46,8 @@ class Delete extends Action ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/delete-index.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Get.php index 9e05cc79c7..3d118d1922 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Get.php @@ -37,8 +37,8 @@ class Get extends Action ->label('scope', 'collections.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/get-index.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/XList.php index c0aa9457e7..60e52f883a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/XList.php @@ -42,8 +42,8 @@ class XList extends Action ->label('scope', 'collections.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/list-indexes.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php index 1309793234..fa95d375c1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php @@ -49,7 +49,7 @@ class XList extends Action ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/get-collection-logs.md', auth: [AuthType::ADMIN], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php index 5bf740d7d1..49870002ce 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php @@ -45,7 +45,7 @@ class Update extends Action ->label('audits.resource', 'database/{request.databaseId}/collections/{request.collectionId}') ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/update-collection.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/XList.php index f2f7a2233a..b4cc5470d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/XList.php @@ -44,7 +44,7 @@ class XList extends Action ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', - group: $this->getSdkGroup(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/list-collections.md', auth: [AuthType::KEY], @@ -121,7 +121,7 @@ class XList extends Action $response->dynamic(new Document([ 'total' => $total, - $this->getSdkGroup() => $collections, + $this->getSDKGroup() => $collections, ]), $this->getResponseModel()); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php new file mode 100644 index 0000000000..512deb8fc8 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php @@ -0,0 +1,67 @@ +context = TABLES; + } + return parent::setHttpPath($path); + } + + /** + * Get the current API context. + */ + protected function getContext(): string + { + return $this->context; + } + + /** + * Determine if the current action is for the Collections API. + */ + protected function isCollectionsAPI(): bool + { + return $this->getContext() === COLLECTIONS; + } + + /** + * Get the key used in event parameters (e.g., 'collectionId' or 'tableId'). + */ + protected function getGroupId(): string + { + return $this->getContext() . 'Id'; + } + + /** + * Get the resource type for the current action (either 'document' or 'row'). + */ + protected function getResource(): string + { + return $this->isCollectionsAPI() ? 'document' : 'row'; + } + + /** + * Get the resource ID key for the current action. + */ + protected function getResourceId(): string + { + return $this->getResource() . 'Id'; + } + + protected function getAttributeKey(): string + { + return $this->isCollectionsAPI() ? 'attribute' : 'column'; + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index d2f114a888..77589e848a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -2,7 +2,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; -use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -19,7 +18,7 @@ class Create extends Action { public static function getName(): string { - return 'createTransaction'; + return 'createDatabasesTransaction'; } protected function getResponseModel(): string diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php index 77dde44bf6..81315498f6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php @@ -3,7 +3,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -18,7 +17,7 @@ class Delete extends Action { public static function getName(): string { - return 'deleteTransaction'; + return 'deleteDatabasesTransaction'; } protected function getResponseModel(): string diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php index 3c7eec7741..6afe28d626 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php @@ -3,7 +3,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -17,7 +16,7 @@ class Get extends Action { public static function getName(): string { - return 'getTransaction'; + return 'getDatabasesTransaction'; } protected function getResponseModel(): string diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 8216d96d5d..58505591ff 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -3,7 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Operations; use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; +use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -21,7 +21,7 @@ class Create extends Action { public static function getName(): string { - return 'createOperations'; + return 'createDatabasesTransactionOperations'; } protected function getResponseModel(): string @@ -84,7 +84,9 @@ class Create extends Action throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $collections[$operation['collectionId']] ??= $dbForProject->getDocument('database_' . $database->getSequence(), $operation['collectionId']); + $collection = $collections[$operation[$this->getGroupId()]] ??= + $dbForProject->getDocument('database_' . $database->getSequence(), $operation[$this->getGroupId()]); + if ($collection->isEmpty()) { throw new Exception(Exception::COLLECTION_NOT_FOUND); } @@ -94,7 +96,7 @@ class Create extends Action 'databaseInternalId' => $database->getSequence(), 'collectionInternalId' => $collection->getSequence(), 'transactionInternalId' => $transaction->getSequence(), - 'documentId' => $operation['documentId'] ?? null, + 'documentId' => $operation[$this->getResourceId()] ?? null, 'action' => $operation['action'], 'data' => $operation['data'] ?? [], ]); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index e854306a47..3e504822f6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; use Appwrite\Event\Delete; use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -27,7 +26,7 @@ class Update extends Action { public static function getName(): string { - return 'updateTransaction'; + return 'updateDatabasesTransaction'; } protected function getResponseModel(): string @@ -73,11 +72,10 @@ class Update extends Action * @param bool $rollback * @param UtopiaResponse $response * @param Database $dbForProject - * @param \Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Delete $queueForDeletes + * @param Delete $queueForDeletes * @return void * @throws ConflictException * @throws Exception - * @throws \DateMalformedStringException * @throws \Throwable * @throws \Utopia\Database\Exception * @throws Authorization @@ -357,7 +355,7 @@ class Update extends Action $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data['attribute'], + attribute: $data[$this->getAttributeKey()], value: $data['value'] ?? 1, max: $data['max'] ?? null ); @@ -369,7 +367,7 @@ class Update extends Action $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data['attribute'], + attribute: $data[$this->getAttributeKey()], value: $data['value'] ?? 1, max: $data['max'] ?? null ); @@ -394,7 +392,7 @@ class Update extends Action $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data['attribute'], + attribute: $data[$this->getAttributeKey()], value: $data['value'] ?? 1, min: $data['min'] ?? null ); @@ -406,7 +404,7 @@ class Update extends Action $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data['attribute'], + attribute: $data[$this->getAttributeKey()], value: $data['value'] ?? 1, min: $data['min'] ?? null ); @@ -447,8 +445,6 @@ class Update extends Action \DateTime $createdAt, array &$state ): void { - \var_dump($data); - $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->updateDocuments( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php index e3bd8144a9..b13ca8c52b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php @@ -3,7 +3,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -20,7 +19,7 @@ class XList extends Action { public static function getName(): string { - return 'listTransactions'; + return 'listDatabasesTransactions'; } protected function getResponseModel(): string diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php index 5222d2e133..8f94ba01ae 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php @@ -37,8 +37,8 @@ class Create extends BooleanCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-boolean-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php index 3c6ef50813..3122227351 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php @@ -39,8 +39,8 @@ class Update extends BooleanUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-boolean-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Create.php index 9598278ffc..db5a3059f1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Create.php @@ -39,8 +39,8 @@ class Create extends DatetimeCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-datetime-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php index d7b5ec2448..151422af75 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php @@ -41,8 +41,8 @@ class Update extends DatetimeUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-datetime-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Delete.php index 50a148ce19..26f4ffa898 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Delete.php @@ -38,8 +38,8 @@ class Delete extends AttributesDelete ->label('audits.event', 'column.delete') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/delete-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php index e28a216fff..3a8d492e4e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php @@ -38,8 +38,8 @@ class Create extends EmailCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-email-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php index 0fb856acb9..4d32489357 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php @@ -40,8 +40,8 @@ class Update extends EmailUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-email-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Create.php index 5b9d89c7e9..68dc2f8e08 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Create.php @@ -40,8 +40,8 @@ class Create extends EnumCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-enum-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Update.php index 0c00e3f268..3b611a5fde 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Update.php @@ -42,8 +42,8 @@ class Update extends EnumUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-enum-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php index 5967b00196..9fe6987cab 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php @@ -38,8 +38,8 @@ class Create extends FloatCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-float-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php index 9486b3a75c..023e2e834e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php @@ -40,8 +40,8 @@ class Update extends FloatUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-float-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Get.php index d536a7aaf2..c20ef58a39 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Get.php @@ -44,8 +44,8 @@ class Get extends AttributesGet ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/get-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php index 325a9382e5..81ca8da81f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php @@ -38,8 +38,8 @@ class Create extends IPCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-ip-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php index b9e6368307..0db95b0206 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php @@ -40,8 +40,8 @@ class Update extends IPUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-ip-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Create.php index bd6fec3f53..dfca51a6c5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Create.php @@ -38,8 +38,8 @@ class Create extends IntegerCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-integer-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Update.php index be92811d1b..a1568d069b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Update.php @@ -40,8 +40,8 @@ class Update extends IntegerUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-integer-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php index f60d4dd5b8..9f3dc3b01e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php @@ -40,8 +40,8 @@ class Create extends LineCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-line-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php index 19c6df202d..cbf0a50de2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php @@ -41,8 +41,8 @@ class Update extends LineUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-line-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php index 47d97e8077..71e51425d6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php @@ -40,8 +40,8 @@ class Create extends PointCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-point-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php index 2e98bf2cf9..ef3e7f6331 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php @@ -41,8 +41,8 @@ class Update extends PointUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-point-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php index 371d5f8fd5..985c264393 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php @@ -40,8 +40,8 @@ class Create extends PolygonCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-polygon-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php index c5654b77d4..d749335e2c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php @@ -41,8 +41,8 @@ class Update extends PolygonUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-polygon-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php index b6f9663f77..0a6c76d8c5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php @@ -39,8 +39,8 @@ class Create extends RelationshipCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-relationship-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php index 421e11af91..b645454be1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php @@ -39,8 +39,8 @@ class Update extends RelationshipUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-relationship-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php index 14f0c8321e..b6cf5fa8c9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php @@ -40,8 +40,8 @@ class Create extends StringCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-string-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php index fc45557f3b..4f3989ac37 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php @@ -42,8 +42,8 @@ class Update extends StringUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-string-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php index bc53ad5250..99ec36b721 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php @@ -38,8 +38,8 @@ class Create extends URLCreate ->label('audits.event', 'column.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/create-url-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Update.php index 36bd7dc054..51168b0383 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Update.php @@ -40,8 +40,8 @@ class Update extends URLUpdate ->label('audits.event', 'column.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-url-column.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/XList.php index ca41bb024d..13bf3257e3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/XList.php @@ -33,8 +33,8 @@ class XList extends AttributesXList ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/list-columns.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php index 3965e12a74..4f62200d7c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php @@ -40,7 +40,7 @@ class Create extends CollectionCreate ->label('audits.event', 'table.create') ->label('audits.resource', 'database/{request.databaseId}/table/{response.$id}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'tables', name: self::getName(), description: '/docs/references/tablesdb/create-table.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php index 9bfdf42cef..de068d5b29 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php @@ -36,7 +36,7 @@ class Delete extends CollectionDelete ->label('audits.event', 'table.delete') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'tables', name: self::getName(), description: '/docs/references/tablesdb/delete-table.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Get.php index a7d33478f7..be6ec5d9e7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Get.php @@ -33,7 +33,7 @@ class Get extends CollectionGet ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'tables', name: self::getName(), description: '/docs/references/tablesdb/get-table.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php index a2a5c8b453..5b61a4bacc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php @@ -42,8 +42,8 @@ class Create extends IndexCreate ->label('audits.event', 'index.create') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: 'createIndex', // getName needs to be different from parent action to avoid conflict in path name description: '/docs/references/tablesdb/create-index.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php index 586bad78f4..9b1583585b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php @@ -41,8 +41,8 @@ class Delete extends IndexDelete ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: 'deleteIndex', // getName needs to be different from parent action to avoid conflict in path name description: '/docs/references/tablesdb/delete-index.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php index 3f2978b547..313260aaee 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php @@ -34,8 +34,8 @@ class Get extends IndexGet ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: 'getIndex', // getName needs to be different from parent action to avoid conflict in path name description: '/docs/references/tablesdb/get-index.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php index c275fd2771..b12cb43f2d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php @@ -34,8 +34,8 @@ class XList extends IndexXList ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: 'listIndexes', // getName needs to be different from parent action to avoid conflict in path name description: '/docs/references/tablesdb/list-indexes.md', auth: [AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php index 6d386df4f6..0680649544 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php @@ -30,8 +30,8 @@ class XList extends CollectionLogXList ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/get-table-logs.md', auth: [AuthType::ADMIN], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php index a46ffe6cba..7d4396b785 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php @@ -40,8 +40,8 @@ class Delete extends DocumentsDelete ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/delete-rows.md', auth: [AuthType::ADMIN, AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php index 1fc8ba031b..5005ab4f41 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php @@ -41,8 +41,8 @@ class Update extends DocumentsUpdate ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-rows.md', auth: [AuthType::ADMIN, AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php index 3f1349e2f7..d0a1f08003 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php @@ -41,8 +41,8 @@ class Upsert extends DocumentsUpsert ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/upsert-rows.md', auth: [AuthType::ADMIN, AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php index 06cfdb5150..d42cf5e9eb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php @@ -41,8 +41,8 @@ class Decrement extends DecrementDocumentAttribute ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/decrement-row-column.md', auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php index bddda28b25..c58e16c8e3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php @@ -41,8 +41,8 @@ class Increment extends IncrementDocumentAttribute ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/increment-row-column.md', auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php index 88fc3c5096..9742208ed6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php @@ -50,8 +50,8 @@ class Create extends DocumentCreate ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), desc: 'Create row', description: '/docs/references/tablesdb/create-row.md', @@ -73,8 +73,8 @@ class Create extends DocumentCreate ] ), new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: $this->getBulkActionName(self::getName()), desc: 'Create rows', description: '/docs/references/tablesdb/create-rows.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php index 0695573ee1..c45029aaab 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php @@ -45,8 +45,8 @@ class Delete extends DocumentDelete ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/delete-row.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -67,6 +67,7 @@ class Delete extends DocumentDelete ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php index 5704f75d82..11faf443d3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php @@ -35,8 +35,8 @@ class Get extends DocumentGet ->label('scope', ['rows.read', 'documents.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/get-row.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -52,9 +52,11 @@ class Get extends DocumentGet ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') ->param('rowId', '', new UID(), 'Row ID.') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') + ->inject('transactionState') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php index a80249070b..5f1efa2953 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php @@ -30,7 +30,7 @@ class XList extends DocumentLogXList ->label('scope', ['rows.read', 'documents.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'logs', name: self::getName(), description: '/docs/references/tablesdb/get-row-logs.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php index 6d60cceb22..cb1d5888cf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php @@ -42,8 +42,8 @@ class Update extends DocumentUpdate ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/update-row.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -66,6 +66,7 @@ class Update extends DocumentUpdate ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php index e16fa67e97..0bc373cc93 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php @@ -43,8 +43,8 @@ class Upsert extends DocumentUpsert ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) ->label('sdk', [ new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/upsert-row.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -69,6 +69,7 @@ class Upsert extends DocumentUpsert ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForStatsUsage') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php index 5d503f1c59..6b7fc699fd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php @@ -35,8 +35,8 @@ class XList extends DocumentXList ->label('scope', ['rows.read', 'documents.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), - group: $this->getSdkGroup(), + namespace: $this->getSDKNamespace(), + group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/tablesdb/list-rows.md', auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT], @@ -51,9 +51,11 @@ class XList extends DocumentXList ->param('databaseId', '', new UID(), 'Database ID.') ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TableDB service [server integration](https://appwrite.io/docs/server/tablesdbdb#tablesdbCreate).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) + ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') + ->inject('transactionState') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php index 0fcdf319d2..8424b37a6d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php @@ -39,7 +39,7 @@ class Update extends CollectionUpdate ->label('audits.event', 'table.update') ->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}') ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'tables', name: self::getName(), description: '/docs/references/tablesdb/update-table.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php index 87f720e689..0fb44ee94a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php @@ -34,7 +34,7 @@ class Get extends CollectionUsageGet ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: null, name: self::getName(), description: '/docs/references/tablesdb/get-table-usage.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/XList.php index d9a92e41b1..a47a972371 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/XList.php @@ -35,7 +35,7 @@ class XList extends CollectionXList ->label('scope', ['tables.read', 'collections.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( - namespace: $this->getSdkNamespace(), + namespace: $this->getSDKNamespace(), group: 'tables', name: self::getName(), description: '/docs/references/tablesdb/list-tables.md', diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php index 3286aecf93..7de95da255 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Registry/Legacy.php @@ -78,6 +78,7 @@ use Utopia\Platform\Service; * - Documents * - Attributes * - Indexes + * - Transactions */ class Legacy extends Base { diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php index 89e095cb53..9bf459b19f 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php @@ -34,25 +34,25 @@ class ACIDTest extends Scope $this->assertEquals(201, $database['headers']['status-code']); $databaseId = $database['body']['$id']; - // Create collection with unique constraint - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create table with unique constraint + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'AtomicityTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Add unique column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -63,7 +63,7 @@ class ACIDTest extends Scope ]); // Add unique index - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/indexes', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -75,12 +75,12 @@ class ACIDTest extends Scope sleep(3); - // Create first document outside transaction - $doc1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create first row outside transaction + $doc1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'email' => 'existing@example.com' ] @@ -108,27 +108,27 @@ class ACIDTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'email' => 'newuser@example.com' // This should succeed ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'email' => 'existing@example.com' // This will fail - duplicate ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'email' => 'anotheruser@example.com' // This should not be created due to atomicity ] @@ -148,26 +148,26 @@ class ACIDTest extends Scope ]); if ($response['headers']['status-code'] === 200) { - // If transaction succeeded, all documents should be created - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // If transaction succeeded, all rows should be created + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - // Should have 4 documents total (1 original + 3 from transaction) + // Should have 4 rows total (1 original + 3 from transaction) // But since we have a unique constraint violation, this might fail - $this->assertGreaterThanOrEqual(1, $documents['body']['total']); + $this->assertGreaterThanOrEqual(1, $rows['body']['total']); } else { $this->assertEquals(409, $response['headers']['status-code']); // Conflict error - // Verify NO new documents were created (atomicity) - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify NO new rows were created (atomicity) + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(1, $documents['body']['total']); // Only the original document - $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + $this->assertEquals(1, $rows['body']['total']); // Only the original row + $this->assertEquals('existing@example.com', $rows['body']['rows'][0]['email']); } } @@ -189,25 +189,25 @@ class ACIDTest extends Scope $this->assertEquals(201, $database['headers']['status-code']); $databaseId = $database['body']['$id']; - // Create collection with required fields and constraints - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create table with required fields and constraints + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'ConsistencyTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Add required string column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -218,7 +218,7 @@ class ACIDTest extends Scope ]); // Add integer column with min/max constraints - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/integer', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -249,9 +249,9 @@ class ACIDTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'required_field' => 'Valid User', 'age' => 25 // Valid age @@ -259,9 +259,9 @@ class ACIDTest extends Scope ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'required_field' => 'Too Young User', 'age' => 10 // Below minimum - will fail constraint @@ -269,9 +269,9 @@ class ACIDTest extends Scope ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ 'required_field' => 'Another Valid User', 'age' => 30 // Valid but should not be created due to transaction failure @@ -293,13 +293,13 @@ class ACIDTest extends Scope $this->assertContains($response['headers']['status-code'], [400, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body'])); - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify no rows were created + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(0, $documents['body']['total']); + $this->assertEquals(0, $rows['body']['total']); } /** @@ -320,15 +320,15 @@ class ACIDTest extends Scope $this->assertEquals(201, $database['headers']['status-code']); $databaseId = $database['body']['$id']; - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create table + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'IsolationTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -336,10 +336,10 @@ class ACIDTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Add counter column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/integer', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -352,12 +352,12 @@ class ACIDTest extends Scope sleep(2); - // Create initial document with counter - $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create initial row with counter + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'documentId' => 'shared_counter', + 'rowId' => 'shared_counter', 'data' => [ 'counter' => 0 ] @@ -396,8 +396,8 @@ class ACIDTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => 'shared_counter', + 'tableId' => $tableId, + 'rowId' => 'shared_counter', 'action' => 'increment', 'data' => [ 'column' => 'counter', @@ -416,8 +416,8 @@ class ACIDTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => 'shared_counter', + 'tableId' => $tableId, + 'rowId' => 'shared_counter', 'action' => 'increment', 'data' => [ 'column' => 'counter', @@ -450,13 +450,13 @@ class ACIDTest extends Scope $this->assertEquals(200, $response2['headers']['status-code']); // Check final value - both increments should be applied - $document = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/shared_counter", array_merge([ + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/shared_counter", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); // Both increments should be applied: 0 + 10 + 5 = 15 - $this->assertEquals(15, $document['body']['counter']); + $this->assertEquals(15, $row['body']['counter']); } /** @@ -477,15 +477,15 @@ class ACIDTest extends Scope $this->assertEquals(201, $database['headers']['status-code']); $databaseId = $database['body']['$id']; - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create table + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'DurabilityTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -494,10 +494,10 @@ class ACIDTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Add column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -529,27 +529,27 @@ class ACIDTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'durable_doc_1', + 'rowId' => 'durable_doc_1', 'data' => [ 'data' => 'Important data 1' ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'durable_doc_2', + 'rowId' => 'durable_doc_2', 'data' => [ 'data' => 'Important data 2' ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'durable_doc_1', + 'rowId' => 'durable_doc_1', 'data' => [ 'data' => 'Updated important data 1' ] @@ -569,33 +569,33 @@ class ACIDTest extends Scope $this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body'])); $this->assertEquals('committed', $response['body']['status']); - // List all documents to see what was created - $allDocs = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // List all rows to see what was created + $allDocs = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertGreaterThan(0, $allDocs['body']['total'], 'Should have created documents. Found: ' . json_encode($allDocs['body'])); + $this->assertGreaterThan(0, $allDocs['body']['total'], 'Should have created rows. Found: ' . json_encode($allDocs['body'])); - // Verify documents exist and have correct data - $document1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + // Verify rows exist and have correct data + $row1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/durable_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(200, $document1['headers']['status-code']); - $this->assertEquals('Updated important data 1', $document1['body']['data']); + $this->assertEquals(200, $row1['headers']['status-code']); + $this->assertEquals('Updated important data 1', $row1['body']['data']); - $document2 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_2", array_merge([ + $row2 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/durable_doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(200, $document2['headers']['status-code']); - $this->assertEquals('Important data 2', $document2['body']['data']); + $this->assertEquals(200, $row2['headers']['status-code']); + $this->assertEquals('Important data 2', $row2['body']['data']); // Further update outside transaction to ensure persistence - $update = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + $update = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/durable_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -607,19 +607,19 @@ class ACIDTest extends Scope $this->assertEquals(200, $update['headers']['status-code']); // Verify the update persisted - $document1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/durable_doc_1", array_merge([ + $row1 = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/durable_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals('Modified outside transaction', $document1['body']['data']); + $this->assertEquals('Modified outside transaction', $row1['body']['data']); - // List all documents to verify total count - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // List all rows to verify total count + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(2, $documents['body']['total']); + $this->assertEquals(2, $rows['body']['total']); } } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index affe44b662..c19100e7ed 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -121,15 +121,15 @@ class TransactionsTest extends Scope $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; - // Create a collection for testing - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create a table for testing + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TransactionOperationsTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -138,11 +138,11 @@ class TransactionsTest extends Scope ], ]); - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; + $this->assertEquals(201, $table['headers']['status-code']); + $tableId = $table['body']['$id']; // Add columns - $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -166,18 +166,18 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc1', + 'rowId' => 'doc1', 'data' => [ 'name' => 'Test Document 1' ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc2', + 'rowId' => 'doc2', 'data' => [ 'name' => 'Test Document 2' ] @@ -197,9 +197,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'doc1', + 'rowId' => 'doc1', 'data' => [ 'name' => 'Updated Document 1' ] @@ -219,9 +219,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => 'invalid_database', - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['name' => 'Test'] ] ] @@ -229,7 +229,7 @@ class TransactionsTest extends Scope $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); - // Test invalid collection ID + // Test invalid table ID $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -238,9 +238,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => 'invalid_collection', + 'tableId' => 'invalid_table', 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['name' => 'Test'] ] ] @@ -267,15 +267,15 @@ class TransactionsTest extends Scope $this->assertEquals(201, $database['headers']['status-code']); $databaseId = $database['body']['$id']; - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create table + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TransactionCommitTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -284,11 +284,11 @@ class TransactionsTest extends Scope ], ]); - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; + $this->assertEquals(201, $table['headers']['status-code']); + $tableId = $table['body']['$id']; // Add columns - $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -320,27 +320,27 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc1', + 'rowId' => 'doc1', 'data' => [ 'name' => 'Test Document 1' ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc2', + 'rowId' => 'doc2', 'data' => [ 'name' => 'Test Document 2' ] ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'doc1', + 'rowId' => 'doc1', 'data' => [ 'name' => 'Updated Document 1' ] @@ -363,18 +363,18 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('committed', $response['body']['status']); - // Verify documents were created - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify rows were created + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(2, $documents['body']['total']); + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertEquals(2, $rows['body']['total']); // Verify the update was applied $doc1Found = false; - foreach ($documents['body']['documents'] as $doc) { + foreach ($rows['body']['rows'] as $doc) { if ($doc['$id'] === 'doc1') { $this->assertEquals('Updated Document 1', $doc['name']); $doc1Found = true; @@ -422,15 +422,15 @@ class TransactionsTest extends Scope $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; - // Create a collection for rollback test - $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + // Create a table for rollback test + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TransactionRollbackTest', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -439,10 +439,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Add column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $collectionId . '/columns/string', array_merge([ + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -463,9 +463,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'rollback_doc', + 'rowId' => 'rollback_doc', 'data' => [ 'value' => 'Should not exist' ] @@ -487,14 +487,14 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('rolledBack', $response['body']['status']); - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify no rows were created + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(0, $documents['body']['total']); + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertEquals(0, $rows['body']['total']); } /** @@ -502,7 +502,7 @@ class TransactionsTest extends Scope */ public function testTransactionExpiration(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -514,12 +514,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -529,10 +529,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -565,9 +565,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['data' => 'Should expire'] ] ] @@ -598,7 +598,7 @@ class TransactionsTest extends Scope */ public function testTransactionSizeLimit(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -610,20 +610,20 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [Permission::create(Role::any())], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -650,9 +650,9 @@ class TransactionsTest extends Scope for ($i = 0; $i < 50; $i++) { $operations[] = [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc_' . $i, + 'rowId' => 'doc_' . $i, 'data' => ['value' => 'Test ' . $i] ]; } @@ -674,8 +674,8 @@ class TransactionsTest extends Scope for ($i = 50; $i < 100; $i++) { $operations[] = [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => 'doc_' . $i, + 'tableId' => $tableId, + 'rowId' => 'doc_' . $i, 'action' => 'create', 'data' => ['value' => 'Test ' . $i] ]; @@ -701,9 +701,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'doc_overflow', + 'rowId' => 'doc_overflow', 'data' => ['value' => 'This should fail'] ] ] @@ -717,7 +717,7 @@ class TransactionsTest extends Scope */ public function testConcurrentTransactionConflicts(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -729,12 +729,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -743,10 +743,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -759,13 +759,13 @@ class TransactionsTest extends Scope sleep(2); - // Create initial document - $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create initial row + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'shared_doc', + 'rowId' => 'shared_doc', 'data' => ['counter' => 100] ]); @@ -787,7 +787,7 @@ class TransactionsTest extends Scope $transactionId1 = $txn1['body']['$id']; $transactionId2 = $txn2['body']['$id']; - // Both transactions try to update the same document + // Both transactions try to update the same row $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId1}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -796,9 +796,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'shared_doc', + 'rowId' => 'shared_doc', 'data' => ['counter' => 200] ] ] @@ -812,9 +812,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'shared_doc', + 'rowId' => 'shared_doc', 'data' => ['counter' => 300] ] ] @@ -842,8 +842,8 @@ class TransactionsTest extends Scope $this->assertEquals(409, $response2['headers']['status-code']); // Conflict - // Verify the document has the value from first transaction - $doc = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/shared_doc", array_merge([ + // Verify the row has the value from first transaction + $doc = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/shared_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -852,11 +852,11 @@ class TransactionsTest extends Scope } /** - * Test deleting a document that's being updated in a transaction + * Test deleting a row that's being updated in a transaction */ public function testDeleteDocumentDuringTransaction(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -868,12 +868,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -883,10 +883,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -898,13 +898,13 @@ class TransactionsTest extends Scope sleep(2); - // Create document - $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create row + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'target_doc', + 'rowId' => 'target_doc', 'data' => ['data' => 'Original'] ]); @@ -928,16 +928,16 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'target_doc', + 'rowId' => 'target_doc', 'data' => ['data' => 'Updated in transaction'] ] ] ]); - // Delete the document outside of transaction - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/target_doc", array_merge([ + // Delete the row outside of transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/target_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -945,7 +945,7 @@ class TransactionsTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); - // Try to commit transaction - should fail because document no longer exists + // Try to commit transaction - should fail because row no longer exists $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -962,7 +962,7 @@ class TransactionsTest extends Scope */ public function testBulkOperations(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -974,12 +974,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -989,10 +989,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1002,7 +1002,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1014,14 +1014,14 @@ class TransactionsTest extends Scope sleep(3); - // Create some initial documents + // Create some initial rows for ($i = 1; $i <= 5; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'existing_' . $i, + 'rowId' => 'existing_' . $i, 'data' => [ 'name' => 'Existing ' . $i, 'category' => 'old' @@ -1048,7 +1048,7 @@ class TransactionsTest extends Scope // Bulk create [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'bulkCreate', 'data' => [ ['$id' => 'bulk_1', 'name' => 'Bulk 1', 'category' => 'new'], @@ -1059,7 +1059,7 @@ class TransactionsTest extends Scope // Bulk update [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'bulkUpdate', 'data' => [ 'queries' => [Query::equal('category', ['old'])->toString()], @@ -1069,7 +1069,7 @@ class TransactionsTest extends Scope // Bulk delete [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'bulkDelete', 'data' => [ 'queries' => [Query::equal('name', ['Existing 5'])->toString()] @@ -1092,20 +1092,20 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Verify results - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - // Should have 7 documents (5 existing - 1 deleted + 3 new) - $this->assertEquals(7, $documents['body']['total']); + // Should have 7 rows (5 existing - 1 deleted + 3 new) + $this->assertEquals(7, $rows['body']['total']); // Check categories were updated $oldCategoryCount = 0; $updatedCategoryCount = 0; $newCategoryCount = 0; - foreach ($documents['body']['documents'] as $doc) { + foreach ($rows['body']['rows'] as $doc) { switch ($doc['category']) { case 'old': $oldCategoryCount++; @@ -1129,7 +1129,7 @@ class TransactionsTest extends Scope */ public function testPartialFailureRollback(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1141,12 +1141,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -1154,10 +1154,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns with constraints - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1170,7 +1170,7 @@ class TransactionsTest extends Scope sleep(2); // Create unique index on email - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/indexes", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/indexes", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1182,13 +1182,13 @@ class TransactionsTest extends Scope sleep(2); - // Create an existing document - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create an existing row + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['email' => 'existing@example.com'] ]); @@ -1210,30 +1210,30 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['email' => 'valid1@example.com'] // Valid ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['email' => 'valid2@example.com'] // Valid ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['email' => 'existing@example.com'] // Will fail - duplicate ], [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['email' => 'valid3@example.com'] // Would be valid but should rollback ], ] @@ -1252,14 +1252,14 @@ class TransactionsTest extends Scope $this->assertEquals(409, $response['headers']['status-code']); // Conflict due to duplicate - // Verify NO new documents were created (atomicity) - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify NO new rows were created (atomicity) + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(1, $documents['body']['total']); // Only the original document - $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['email']); + $this->assertEquals(1, $rows['body']['total']); // Only the original row + $this->assertEquals('existing@example.com', $rows['body']['rows'][0]['email']); } /** @@ -1267,7 +1267,7 @@ class TransactionsTest extends Scope */ public function testDoubleCommitRollback(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1279,20 +1279,20 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [Permission::create(Role::any())], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1322,9 +1322,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => ['data' => 'Test'] ] ] @@ -1385,11 +1385,11 @@ class TransactionsTest extends Scope } /** - * Test operations on non-existent documents + * Test operations on non-existent rows */ public function testOperationsOnNonExistentDocuments(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1401,12 +1401,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -1415,10 +1415,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1439,7 +1439,7 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Try to update non-existent document + // Try to update non-existent row $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1448,9 +1448,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'update', - 'documentId' => 'non_existent_doc', + 'rowId' => 'non_existent_doc', 'data' => ['data' => 'Should fail'] ] ] @@ -1469,7 +1469,7 @@ class TransactionsTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // Document not found - // Test delete non-existent document + // Test delete non-existent row $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1486,9 +1486,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'delete', - 'documentId' => 'non_existent_doc', + 'rowId' => 'non_existent_doc', 'data' => [] ] ] @@ -1513,7 +1513,7 @@ class TransactionsTest extends Scope */ public function testCreateDocument(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1525,14 +1525,14 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', - 'documentSecurity' => false, + 'rowSecurity' => false, 'permissions' => [ Permission::create(Role::any()), Permission::read(Role::any()), @@ -1541,7 +1541,7 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns $columns = [ @@ -1555,7 +1555,7 @@ class TransactionsTest extends Scope $type = $attr['type']; unset($attr['type']); - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/{$type}", array_merge([ + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/{$type}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1576,13 +1576,13 @@ class TransactionsTest extends Scope $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; - // Create document via normal route with transactionId - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create row via normal route with transactionId + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_from_route', + 'rowId' => 'doc_from_route', 'data' => [ 'name' => 'Created via normal route', 'counter' => 100, @@ -1594,7 +1594,7 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); // Document should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_from_route", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_from_route", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1613,7 +1613,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Document should now exist - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_from_route", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_from_route", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1627,7 +1627,7 @@ class TransactionsTest extends Scope */ public function testUpdateDocument(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1639,12 +1639,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -1653,10 +1653,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1666,7 +1666,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1677,7 +1677,7 @@ class TransactionsTest extends Scope 'max' => 10000, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1689,13 +1689,13 @@ class TransactionsTest extends Scope sleep(3); - // Create document outside transaction - $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create row outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_to_update', + 'rowId' => 'doc_to_update', 'data' => [ 'name' => 'Original name', 'counter' => 50, @@ -1714,8 +1714,8 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Update document via normal route with transactionId - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + // Update row via normal route with transactionId + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_update", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1731,7 +1731,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Document should still have original values outside transaction - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_update", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1751,7 +1751,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Document should now have updated values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_update", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_update", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1765,7 +1765,7 @@ class TransactionsTest extends Scope */ public function testUpsertDocument(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1777,12 +1777,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -1791,10 +1791,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1804,7 +1804,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1826,13 +1826,13 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Upsert document (create) via normal route with transactionId - $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + // Upsert row (create) via normal route with transactionId + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_upsert", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_upsert', + 'rowId' => 'doc_upsert', 'data' => [ 'name' => 'Created by upsert', 'counter' => 25 @@ -1843,20 +1843,20 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); // Document should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_upsert", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code']); - // Upsert same document (update) in same transaction - $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + // Upsert same row (update) in same transaction + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_upsert", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_upsert', + 'rowId' => 'doc_upsert', 'data' => [ 'name' => 'Updated by upsert', 'counter' => 75 @@ -1878,7 +1878,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Document should now exist with updated values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_upsert", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_upsert", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1893,7 +1893,7 @@ class TransactionsTest extends Scope */ public function testDeleteDocument(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1905,12 +1905,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -1919,10 +1919,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1934,13 +1934,13 @@ class TransactionsTest extends Scope sleep(2); - // Create document outside transaction - $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create row outside transaction + $doc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_to_delete', + 'rowId' => 'doc_to_delete', 'data' => ['name' => 'Will be deleted'] ]); @@ -1955,8 +1955,8 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Delete document via normal route with transactionId - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + // Delete row via normal route with transactionId + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_delete", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1967,7 +1967,7 @@ class TransactionsTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); // Document should still exist outside transaction - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_delete", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1986,7 +1986,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Document should no longer exist - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_to_delete", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_to_delete", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1999,7 +1999,7 @@ class TransactionsTest extends Scope */ public function testBulkCreate(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2011,12 +2011,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2024,10 +2024,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2037,7 +2037,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2059,12 +2059,12 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; // Bulk create via normal route with transactionId - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documents' => [ + 'rows' => [ [ '$id' => 'bulk_create_1', 'name' => 'Bulk created 1', @@ -2087,7 +2087,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Bulk operations return 200 // Documents should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2096,8 +2096,8 @@ class TransactionsTest extends Scope $this->assertEquals(0, $response['body']['total']); - // Individual document check - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_create_1", array_merge([ + // Individual row check + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_create_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2116,7 +2116,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Documents should now exist - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2125,9 +2125,9 @@ class TransactionsTest extends Scope $this->assertEquals(3, $response['body']['total']); - // Verify individual documents + // Verify individual rows for ($i = 1; $i <= 3; $i++) { - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_create_{$i}", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_create_{$i}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2143,7 +2143,7 @@ class TransactionsTest extends Scope */ public function testBulkUpdate(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2155,12 +2155,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2169,10 +2169,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2182,7 +2182,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2194,14 +2194,14 @@ class TransactionsTest extends Scope sleep(3); - // Create documents for bulk testing + // Create rows for bulk testing for ($i = 1; $i <= 3; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'bulk_update_' . $i, + 'rowId' => 'bulk_update_' . $i, 'data' => [ 'name' => 'Bulk doc ' . $i, 'category' => 'bulk_test' @@ -2219,7 +2219,7 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; // Bulk update via normal route with transactionId - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2232,7 +2232,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Documents should still have original category outside transaction - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2253,7 +2253,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Documents should now have updated category - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2268,7 +2268,7 @@ class TransactionsTest extends Scope */ public function testBulkUpsert(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2280,12 +2280,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2294,10 +2294,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2307,7 +2307,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2320,13 +2320,13 @@ class TransactionsTest extends Scope sleep(3); - // Create one document outside transaction - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create one row outside transaction + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'bulk_upsert_existing', + 'rowId' => 'bulk_upsert_existing', 'data' => [ 'name' => 'Existing doc', 'counter' => 10 @@ -2343,12 +2343,12 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; // Bulk upsert via normal route with transactionId (updates existing, creates new) - $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documents' => [ + 'rows' => [ [ '$id' => 'bulk_upsert_existing', 'name' => 'Updated existing', @@ -2365,8 +2365,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Original document should be unchanged, new document shouldn't exist outside transaction - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_existing", array_merge([ + // Original row should be unchanged, new row shouldn't exist outside transaction + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_upsert_existing", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2374,7 +2374,7 @@ class TransactionsTest extends Scope $this->assertEquals('Existing doc', $response['body']['name']); $this->assertEquals(10, $response['body']['counter']); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_new", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_upsert_new", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2392,8 +2392,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Check both documents exist with updated values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_existing", array_merge([ + // Check both rows exist with updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_upsert_existing", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2401,7 +2401,7 @@ class TransactionsTest extends Scope $this->assertEquals('Updated existing', $response['body']['name']); $this->assertEquals(20, $response['body']['counter']); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/bulk_upsert_new", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/bulk_upsert_new", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2415,7 +2415,7 @@ class TransactionsTest extends Scope */ public function testBulkDelete(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2427,12 +2427,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2441,10 +2441,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2454,7 +2454,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2466,14 +2466,14 @@ class TransactionsTest extends Scope sleep(3); - // Create documents for bulk testing + // Create rows for bulk testing for ($i = 1; $i <= 3; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'bulk_delete_' . $i, + 'rowId' => 'bulk_delete_' . $i, 'data' => [ 'name' => 'Delete doc ' . $i, 'category' => 'bulk_delete_test' @@ -2491,7 +2491,7 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; // Bulk delete via normal route with transactionId - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2503,7 +2503,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Bulk delete with transaction returns 200 // Documents should still exist outside transaction - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2524,7 +2524,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); // Documents should now be deleted - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2539,7 +2539,7 @@ class TransactionsTest extends Scope */ public function testMixedSingleOperations(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2551,12 +2551,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2566,10 +2566,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2579,7 +2579,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2589,7 +2589,7 @@ class TransactionsTest extends Scope 'required' => false, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2602,13 +2602,13 @@ class TransactionsTest extends Scope sleep(3); - // Create an existing document outside transaction for testing - $existingDoc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Create an existing row outside transaction for testing + $existingDoc = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'existing_doc', + 'rowId' => 'existing_doc', 'data' => [ 'name' => 'Existing Document', 'status' => 'active', @@ -2628,13 +2628,13 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; $this->assertEquals(201, $transaction['headers']['status-code']); - // 1. Create new document via normal route with transactionId - $response1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // 1. Create new row via normal route with transactionId + $response1 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'new_doc_1', + 'rowId' => 'new_doc_1', 'data' => [ 'name' => 'New Document 1', 'status' => 'pending', @@ -2645,13 +2645,13 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response1['headers']['status-code']); - // 2. Create another document via normal route with transactionId - $response2 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // 2. Create another row via normal route with transactionId + $response2 = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'new_doc_2', + 'rowId' => 'new_doc_2', 'data' => [ 'name' => 'New Document 2', 'status' => 'pending', @@ -2662,8 +2662,8 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response2['headers']['status-code']); - // 3. Update existing document via normal route with transactionId - $response3 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + // 3. Update existing row via normal route with transactionId + $response3 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/existing_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2677,8 +2677,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response3['headers']['status-code']); - // 4. Update the first new document (created in same transaction) - $response4 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + // 4. Update the first new row (created in same transaction) + $response4 = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/new_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2692,8 +2692,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response4['headers']['status-code']); - // 5. Delete the second new document (created in same transaction) - $response5 = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_2", array_merge([ + // 5. Delete the second new row (created in same transaction) + $response5 = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/new_doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2703,13 +2703,13 @@ class TransactionsTest extends Scope $this->assertEquals(204, $response5['headers']['status-code']); - // 6. Upsert a new document via normal route with transactionId - $response6 = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + // 6. Upsert a new row via normal route with transactionId + $response6 = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/upserted_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'upserted_doc', + 'rowId' => 'upserted_doc', 'data' => [ 'name' => 'Upserted Document', 'status' => 'new', @@ -2731,14 +2731,14 @@ class TransactionsTest extends Scope $this->assertEquals(6, $txnDetails['body']['operations']); // 6 operations total // Verify nothing exists outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/new_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/upserted_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2746,7 +2746,7 @@ class TransactionsTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // Existing doc should still have original values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/existing_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2768,7 +2768,7 @@ class TransactionsTest extends Scope // Verify final state after commit // new_doc_1 should exist with updated values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_1", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/new_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2779,7 +2779,7 @@ class TransactionsTest extends Scope $this->assertEquals(8, $response['body']['priority']); // new_doc_2 should not exist (was deleted in transaction) - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/new_doc_2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/new_doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2787,7 +2787,7 @@ class TransactionsTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // existing_doc should have updated values - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_doc", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/existing_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2796,7 +2796,7 @@ class TransactionsTest extends Scope $this->assertEquals(10, $response['body']['priority']); // upserted_doc should exist - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/upserted_doc", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/upserted_doc", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2806,13 +2806,13 @@ class TransactionsTest extends Scope $this->assertEquals('new', $response['body']['status']); $this->assertEquals(3, $response['body']['priority']); - // Verify total document count - $documents = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Verify total row count + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(3, $documents['body']['total']); // existing_doc, new_doc_1, upserted_doc + $this->assertEquals(3, $rows['body']['total']); // existing_doc, new_doc_1, upserted_doc } /** @@ -2820,7 +2820,7 @@ class TransactionsTest extends Scope */ public function testMixedOperations(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2832,12 +2832,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::create(Role::any()), @@ -2847,10 +2847,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create column - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -2880,9 +2880,9 @@ class TransactionsTest extends Scope 'operations' => [ [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'action' => 'create', - 'documentId' => 'mixed_doc1', + 'rowId' => 'mixed_doc1', 'data' => ['name' => 'Via Operations Add'] ] ] @@ -2892,12 +2892,12 @@ class TransactionsTest extends Scope $this->assertEquals(1, $response['body']['operations']); // Add operation via normal route with transactionId - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'mixed_doc2', + 'rowId' => 'mixed_doc2', 'data' => ['name' => 'Via normal route'], 'transactionId' => $transactionId ]); @@ -2913,15 +2913,15 @@ class TransactionsTest extends Scope $this->assertEquals(2, $txnDetails['body']['operations']); - // Both documents shouldn't exist yet - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc1", array_merge([ + // Both rows shouldn't exist yet + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/mixed_doc1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/mixed_doc2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2939,8 +2939,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Both documents should now exist - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc1", array_merge([ + // Both rows should now exist + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/mixed_doc1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2948,7 +2948,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Via Operations Add', $response['body']['name']); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/mixed_doc2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/mixed_doc2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -2958,11 +2958,11 @@ class TransactionsTest extends Scope } /** - * Test bulk update with queries that should match documents created in the same transaction + * Test bulk update with queries that should match rows created in the same transaction */ public function testBulkUpdateWithTransactionAwareQueries(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2974,12 +2974,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -2989,10 +2989,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3002,7 +3002,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/integer", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3011,7 +3011,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3023,14 +3023,14 @@ class TransactionsTest extends Scope sleep(3); // Wait for columns to be created - // Create some existing documents + // Create some existing rows for ($i = 1; $i <= 3; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'existing_' . $i, + 'rowId' => 'existing_' . $i, 'data' => [ 'name' => 'Existing ' . $i, 'age' => 20 + $i, @@ -3048,13 +3048,13 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Step 1: Create new documents with age > 25 in transaction - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Step 1: Create new rows with age > 25 in transaction + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'txn_doc_1', + 'rowId' => 'txn_doc_1', 'data' => [ 'name' => 'Transaction Doc 1', 'age' => 30, @@ -3065,12 +3065,12 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'txn_doc_2', + 'rowId' => 'txn_doc_2', 'data' => [ 'name' => 'Transaction Doc 2', 'age' => 35, @@ -3081,10 +3081,10 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); - // Step 2: Bulk update all documents with age > 25 to have status 'active' - // This should match both existing_3 (age=23 doesn't match, age=24 doesn't match, but existing documents have age 21,22,23) + // Step 2: Bulk update all rows with age > 25 to have status 'active' + // This should match both existing_3 (age=23 doesn't match, age=24 doesn't match, but existing rows have age 21,22,23) // Wait, let me fix the ages - existing docs have ages 21, 22, 23, so only txn docs should match - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3109,8 +3109,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Verify that documents created in the transaction were updated by the bulk update - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/txn_doc_1", array_merge([ + // Verify that rows created in the transaction were updated by the bulk update + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/txn_doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -3118,7 +3118,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/txn_doc_2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/txn_doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -3126,24 +3126,24 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('active', $response['body']['status'], 'Document created in transaction should be updated by bulk update query'); - // Verify existing documents were not affected + // Verify existing rows were not affected for ($i = 1; $i <= 3; $i++) { - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_{$i}", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/existing_{$i}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('inactive', $response['body']['status'], "Existing document {$i} should remain inactive (age <= 25)"); + $this->assertEquals('inactive', $response['body']['status'], "Existing row {$i} should remain inactive (age <= 25)"); } } /** - * Test bulk update with queries that should match documents updated in the same transaction + * Test bulk update with queries that should match rows updated in the same transaction */ public function testBulkUpdateMatchingUpdatedDocuments(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -3155,12 +3155,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -3170,10 +3170,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3183,7 +3183,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3193,7 +3193,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3205,14 +3205,14 @@ class TransactionsTest extends Scope sleep(3); // Wait for columns to be created - // Create existing documents + // Create existing rows for ($i = 1; $i <= 4; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_' . $i, + 'rowId' => 'doc_' . $i, 'data' => [ 'name' => 'Document ' . $i, 'category' => 'normal', @@ -3230,8 +3230,8 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Step 1: Update some documents to have category 'special' in transaction - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_1", array_merge([ + // Step 1: Update some rows to have category 'special' in transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3244,7 +3244,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3257,9 +3257,9 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Step 2: Bulk update all documents with category 'special' to have priority 'high' - // This should match the documents we just updated in the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Step 2: Bulk update all rows with category 'special' to have priority 'high' + // This should match the rows we just updated in the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3284,8 +3284,8 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Verify that the updated documents were matched by bulk update - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_1", array_merge([ + // Verify that the updated rows were matched by bulk update + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -3294,7 +3294,7 @@ class TransactionsTest extends Scope $this->assertEquals('special', $response['body']['category']); $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -3303,8 +3303,8 @@ class TransactionsTest extends Scope $this->assertEquals('special', $response['body']['category']); $this->assertEquals('high', $response['body']['priority'], 'Document updated in transaction should be matched by bulk update query'); - // Verify other documents were not affected - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_3", array_merge([ + // Verify other rows were not affected + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_3", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -3315,11 +3315,11 @@ class TransactionsTest extends Scope } /** - * Test bulk delete with queries that should match documents created in the same transaction + * Test bulk delete with queries that should match rows created in the same transaction */ public function testBulkDeleteMatchingCreatedDocuments(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -3331,12 +3331,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -3346,10 +3346,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3359,7 +3359,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3371,14 +3371,14 @@ class TransactionsTest extends Scope sleep(3); // Wait for columns to be created - // Create existing documents + // Create existing rows for ($i = 1; $i <= 3; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'existing_' . $i, + 'rowId' => 'existing_' . $i, 'data' => [ 'name' => 'Existing ' . $i, 'type' => 'permanent' @@ -3395,13 +3395,13 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Step 1: Create temporary documents in transaction - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Step 1: Create temporary rows in transaction + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'temp_1', + 'rowId' => 'temp_1', 'data' => [ 'name' => 'Temporary 1', 'type' => 'temporary' @@ -3411,12 +3411,12 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'temp_2', + 'rowId' => 'temp_2', 'data' => [ 'name' => 'Temporary 2', 'type' => 'temporary' @@ -3426,9 +3426,9 @@ class TransactionsTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); - // Step 2: Bulk delete all documents with type 'temporary' - // This should delete the documents we just created in the transaction - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Step 2: Bulk delete all rows with type 'temporary' + // This should delete the rows we just created in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3450,39 +3450,39 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Verify temporary documents were deleted (should not exist) - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/temp_1", array_merge([ + // Verify temporary rows were deleted (should not exist) + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/temp_1", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary row created and deleted in transaction should not exist'); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/temp_2", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/temp_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(404, $response['headers']['status-code'], 'Temporary document created and deleted in transaction should not exist'); + $this->assertEquals(404, $response['headers']['status-code'], 'Temporary row created and deleted in transaction should not exist'); - // Verify existing documents were not affected + // Verify existing rows were not affected for ($i = 1; $i <= 3; $i++) { - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/existing_{$i}", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/existing_{$i}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(200, $response['headers']['status-code'], "Permanent document {$i} should still exist"); + $this->assertEquals(200, $response['headers']['status-code'], "Permanent row {$i} should still exist"); $this->assertEquals('permanent', $response['body']['type']); } } /** - * Test bulk delete with queries that should match documents updated in the same transaction + * Test bulk delete with queries that should match rows updated in the same transaction */ public function testBulkDeleteMatchingUpdatedDocuments(): void { - // Create database and collection + // Create database and table $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -3494,12 +3494,12 @@ class TransactionsTest extends Scope $databaseId = $database['body']['$id']; - $collection = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'collectionId' => ID::unique(), + 'tableId' => ID::unique(), 'name' => 'TestCollection', 'permissions' => [ Permission::read(Role::any()), @@ -3509,10 +3509,10 @@ class TransactionsTest extends Scope ], ]); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; // Create columns - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3522,7 +3522,7 @@ class TransactionsTest extends Scope 'required' => true, ]); - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/columns/string", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3534,14 +3534,14 @@ class TransactionsTest extends Scope sleep(3); // Wait for columns to be created - // Create existing documents + // Create existing rows for ($i = 1; $i <= 5; $i++) { - $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'documentId' => 'doc_' . $i, + 'rowId' => 'doc_' . $i, 'data' => [ 'name' => 'Document ' . $i, 'status' => 'active' @@ -3558,8 +3558,8 @@ class TransactionsTest extends Scope $transactionId = $transaction['body']['$id']; - // Step 1: Mark some documents for deletion by updating their status - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + // Step 1: Mark some rows for deletion by updating their status + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3572,7 +3572,7 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_4", array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_4", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3585,9 +3585,9 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Step 2: Bulk delete all documents with status 'marked_for_deletion' - // This should delete the documents we just updated in the transaction - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows", array_merge([ + // Step 2: Bulk delete all rows with status 'marked_for_deletion' + // This should delete the rows we just updated in the transaction + $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -3609,24 +3609,24 @@ class TransactionsTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - // Verify marked documents were deleted - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_2", array_merge([ + // Verify marked rows were deleted + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_2", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_4", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_4", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code'], 'Document marked for deletion should have been deleted'); - // Verify other documents still exist + // Verify other rows still exist foreach ([1, 3, 5] as $i) { - $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$collectionId}/rows/doc_{$i}", array_merge([ + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc_{$i}", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); From 55bebd92f3e90f52c04359627899771640ede713 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 06:27:58 +0000 Subject: [PATCH 131/385] Fix date format --- app/controllers/api/account.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index eafff19073..f6a2760cde 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -213,7 +213,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res 'ip' => $request->getIP(), 'factors' => [$factor], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::addSeconds(new \DateTime(), $duration) + 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) ], $detector->getOS(), $detector->getClient(), @@ -838,7 +838,7 @@ App::patch('/v1/account/sessions/:sessionId') // Extend session $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; - $session->setAttribute('expire', DateTime::addSeconds(new \DateTime(), $authDuration)); + $session->setAttribute('expire', DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $authDuration))); // Refresh OAuth access token $provider = $session->getAttribute('provider', ''); @@ -950,7 +950,7 @@ App::post('/v1/account/sessions/email') 'ip' => $request->getIP(), 'factors' => ['password'], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::addSeconds(new \DateTime(), $duration) + 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) ], $detector->getOS(), $detector->getClient(), @@ -1117,7 +1117,7 @@ App::post('/v1/account/sessions/anonymous') 'ip' => $request->getIP(), 'factors' => ['anonymous'], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::addSeconds(new \DateTime(), $duration) + 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) ], $detector->getOS(), $detector->getClient(), @@ -1772,7 +1772,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'ip' => $request->getIP(), 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::addSeconds(new \DateTime(), $duration) + 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); $session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [ @@ -3352,7 +3352,7 @@ App::post('/v1/account/recovery') throw new Exception(Exception::USER_BLOCKED); } - $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_RECOVERY); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_RECOVERY)); $secret = $proofForToken->generate(); $recovery = new Document([ @@ -3616,7 +3616,7 @@ App::post('/v1/account/verification') } $verificationSecret = $proofForToken->generate(); - $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $verification = new Document([ '$id' => ID::unique(), @@ -3868,7 +3868,7 @@ App::post('/v1/account/verification/phone') } $secret ??= $proofForCode->generate(); - $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $verification = new Document([ '$id' => ID::unique(), @@ -4639,7 +4639,7 @@ App::post('/v1/account/mfa/challenge') ->inject('proofForCode') ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsToken $proofForToken, ProofsCode $proofForCode) { - $expire = DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $code = $proofForCode->generate(); $challenge = new Document([ From 6d19d76bac375a428e7e8c7bdecbb33a97b93d2e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 06:51:01 +0000 Subject: [PATCH 132/385] Fix scope check --- app/controllers/shared/api.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index e84699274f..4f7e351084 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -434,9 +434,9 @@ App::init() } // Step 9: Validate scope permissions - $scope = $route->getLabel('scope', 'none'); - if (!\in_array($scope, $scopes)) { - throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')'); + $allowed = (array)$route->getLabel('scope', 'none'); + if (empty(\array_intersect($allowed, $scopes))) { + throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')'); } // Step 10: Check if user is blocked From e4d70a4d4f87435108cca8c09c5b9e2731c6624c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 07:55:52 +0000 Subject: [PATCH 133/385] Fix: default options --- app/config/collections/common.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/config/collections/common.php b/app/config/collections/common.php index a291f7b19d..804929fcfd 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1,5 +1,6 @@ 256, 'signed' => true, 'required' => false, - 'default' => '', + 'default' => (new Argon2())->getName(), 'array' => false, 'filters' => [], ], @@ -183,7 +184,7 @@ return [ 'size' => 65535, 'signed' => true, 'required' => false, - 'default' => new \stdClass(), + 'default' => (new Argon2())->getOptions(), 'array' => false, 'filters' => ['json'], ], From 72186f18826cda51bee83a64e8a71a653e8a6f19 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 08:46:38 +0000 Subject: [PATCH 134/385] reset det change --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index f6a2760cde..02b8c92125 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -213,7 +213,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res 'ip' => $request->getIP(), 'factors' => [$factor], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) + 'expire' => DateTime::addSeconds(new \DateTime(), $duration) ], $detector->getOS(), $detector->getClient(), From bcec5c0922475d3b621b9667e691a30fca16866c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 08:57:27 +0000 Subject: [PATCH 135/385] revert date format --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 02b8c92125..ccf67c5095 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -838,7 +838,7 @@ App::patch('/v1/account/sessions/:sessionId') // Extend session $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; - $session->setAttribute('expire', DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $authDuration))); + $session->setAttribute('expire', DateTime::addSeconds(new \DateTime(), $authDuration)); // Refresh OAuth access token $provider = $session->getAttribute('provider', ''); From e36de6b72ba4f3df9a3f088f9788bf8d19a2527f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 08:59:27 +0000 Subject: [PATCH 136/385] revert date format --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ccf67c5095..c6b8131680 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -950,7 +950,7 @@ App::post('/v1/account/sessions/email') 'ip' => $request->getIP(), 'factors' => ['password'], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) + 'expire' => DateTime::addSeconds(new \DateTime(), $duration) ], $detector->getOS(), $detector->getClient(), From 8706828c2235fa3863997be9590c33aed991e1af Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 21:13:41 +1200 Subject: [PATCH 137/385] Fix tests --- .../Databases/Http/Databases/Transactions/Update.php | 7 +++++-- .../Databases/TablesDB/Transactions/TransactionsTest.php | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 3e504822f6..dde39efd90 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -271,7 +271,7 @@ class Update extends Action new Document($data), ); if ($document->isEmpty()) { - throw new ConflictException(''); + throw new NotFoundException(''); } $state[$collectionId][$documentId] = $document; }); @@ -330,7 +330,10 @@ class Update extends Action // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, &$state) { - $dbForProject->deleteDocument($collectionId, $documentId); + $deleted = $dbForProject->deleteDocument($collectionId, $documentId); + if (!$deleted) { + throw new NotFoundException(''); + } if (isset($state[$collectionId][$documentId])) { unset($state[$collectionId][$documentId]); } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index c19100e7ed..9ec5994903 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -954,7 +954,7 @@ class TransactionsTest extends Scope 'commit' => true ]); - $this->assertEquals(409, $response['headers']['status-code']); // Conflict + $this->assertEquals(404, $response['headers']['status-code']); } /** From 72f5793928fcc5d7c28a6ec6d6f399c4df51d2b0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 09:18:23 +0000 Subject: [PATCH 138/385] revert date format --- app/controllers/api/account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c6b8131680..059dce5fc1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1117,7 +1117,7 @@ App::post('/v1/account/sessions/anonymous') 'ip' => $request->getIP(), 'factors' => ['anonymous'], 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) + 'expire' => DateTime::addSeconds(new \DateTime(), $duration) ], $detector->getOS(), $detector->getClient(), @@ -1772,7 +1772,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'ip' => $request->getIP(), 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)) + 'expire' => DateTime::addSeconds(new \DateTime(), $duration) ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); $session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [ From be81c73c4076ee48889fd215f20e740b9b382b98 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 21:30:07 +1200 Subject: [PATCH 139/385] Update DB --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index aa33f5ff1b..ba8b58fcea 100644 --- a/composer.lock +++ b/composer.lock @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" + "reference": "44af82dcb44cdaa4b2d7f30528903f186a972ee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/44af82dcb44cdaa4b2d7f30528903f186a972ee0", + "reference": "44af82dcb44cdaa4b2d7f30528903f186a972ee0", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.0.0" + "source": "https://github.com/utopia-php/database/tree/2.0.1" }, - "time": "2025-09-04T12:36:53+00:00" + "time": "2025-09-11T08:33:25+00:00" }, { "name": "utopia-php/detector", From dd64afa97035a0608ee89be94e876a1e751d4f06 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 22:16:44 +1200 Subject: [PATCH 140/385] Add missing references --- docs/references/databases/create-operations.md | 0 docs/references/databases/create-transaction.md | 0 docs/references/databases/delete-transaction.md | 0 docs/references/databases/get-transaction.md | 0 docs/references/databases/list-transactions.md | 0 docs/references/databases/update-transaction.md | 0 docs/references/tablesdb/create-operations.md | 1 + docs/references/tablesdb/create-transaction.md | 1 + docs/references/tablesdb/delete-transaction.md | 1 + docs/references/tablesdb/get-transaction.md | 1 + docs/references/tablesdb/list-transactions.md | 1 + docs/references/tablesdb/update-transaction.md | 1 + 12 files changed, 6 insertions(+) create mode 100644 docs/references/databases/create-operations.md create mode 100644 docs/references/databases/create-transaction.md create mode 100644 docs/references/databases/delete-transaction.md create mode 100644 docs/references/databases/get-transaction.md create mode 100644 docs/references/databases/list-transactions.md create mode 100644 docs/references/databases/update-transaction.md create mode 100644 docs/references/tablesdb/create-operations.md create mode 100644 docs/references/tablesdb/create-transaction.md create mode 100644 docs/references/tablesdb/delete-transaction.md create mode 100644 docs/references/tablesdb/get-transaction.md create mode 100644 docs/references/tablesdb/list-transactions.md create mode 100644 docs/references/tablesdb/update-transaction.md diff --git a/docs/references/databases/create-operations.md b/docs/references/databases/create-operations.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/databases/create-transaction.md b/docs/references/databases/create-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/databases/delete-transaction.md b/docs/references/databases/delete-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/databases/get-transaction.md b/docs/references/databases/get-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/databases/list-transactions.md b/docs/references/databases/list-transactions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/databases/update-transaction.md b/docs/references/databases/update-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/references/tablesdb/create-operations.md b/docs/references/tablesdb/create-operations.md new file mode 100644 index 0000000000..a737b95a55 --- /dev/null +++ b/docs/references/tablesdb/create-operations.md @@ -0,0 +1 @@ +Create multiple operations in a single transaction. \ No newline at end of file diff --git a/docs/references/tablesdb/create-transaction.md b/docs/references/tablesdb/create-transaction.md new file mode 100644 index 0000000000..fdf369a789 --- /dev/null +++ b/docs/references/tablesdb/create-transaction.md @@ -0,0 +1 @@ +Create a new transaction. \ No newline at end of file diff --git a/docs/references/tablesdb/delete-transaction.md b/docs/references/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..f1395c228f --- /dev/null +++ b/docs/references/tablesdb/delete-transaction.md @@ -0,0 +1 @@ +Delete a transaction by its unique ID. \ No newline at end of file diff --git a/docs/references/tablesdb/get-transaction.md b/docs/references/tablesdb/get-transaction.md new file mode 100644 index 0000000000..41900f7468 --- /dev/null +++ b/docs/references/tablesdb/get-transaction.md @@ -0,0 +1 @@ +Get a transaction by its unique ID. \ No newline at end of file diff --git a/docs/references/tablesdb/list-transactions.md b/docs/references/tablesdb/list-transactions.md new file mode 100644 index 0000000000..9a63d9f04a --- /dev/null +++ b/docs/references/tablesdb/list-transactions.md @@ -0,0 +1 @@ +List transactions across all databases. \ No newline at end of file diff --git a/docs/references/tablesdb/update-transaction.md b/docs/references/tablesdb/update-transaction.md new file mode 100644 index 0000000000..d9d5f45439 --- /dev/null +++ b/docs/references/tablesdb/update-transaction.md @@ -0,0 +1 @@ +Update a transaction, to either commit or roll back its operations. \ No newline at end of file From c7dba3690a125f598e8c2d33080320e910e8facd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 22:17:00 +1200 Subject: [PATCH 141/385] Update auth types --- docs/references/databases/create-operations.md | 1 + docs/references/databases/create-transaction.md | 1 + docs/references/databases/delete-transaction.md | 1 + docs/references/databases/get-index.md | 2 +- docs/references/databases/get-transaction.md | 1 + docs/references/databases/list-transactions.md | 1 + docs/references/databases/update-transaction.md | 1 + .../Modules/Databases/Http/Databases/Transactions/Create.php | 2 +- .../Modules/Databases/Http/Databases/Transactions/Delete.php | 2 +- .../Modules/Databases/Http/Databases/Transactions/Get.php | 2 +- .../Modules/Databases/Http/Databases/Transactions/Update.php | 2 +- .../Modules/Databases/Http/Databases/Transactions/XList.php | 2 +- 12 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/references/databases/create-operations.md b/docs/references/databases/create-operations.md index e69de29bb2..a737b95a55 100644 --- a/docs/references/databases/create-operations.md +++ b/docs/references/databases/create-operations.md @@ -0,0 +1 @@ +Create multiple operations in a single transaction. \ No newline at end of file diff --git a/docs/references/databases/create-transaction.md b/docs/references/databases/create-transaction.md index e69de29bb2..fdf369a789 100644 --- a/docs/references/databases/create-transaction.md +++ b/docs/references/databases/create-transaction.md @@ -0,0 +1 @@ +Create a new transaction. \ No newline at end of file diff --git a/docs/references/databases/delete-transaction.md b/docs/references/databases/delete-transaction.md index e69de29bb2..f1395c228f 100644 --- a/docs/references/databases/delete-transaction.md +++ b/docs/references/databases/delete-transaction.md @@ -0,0 +1 @@ +Delete a transaction by its unique ID. \ No newline at end of file diff --git a/docs/references/databases/get-index.md b/docs/references/databases/get-index.md index cdea5b4f27..cdc27fa967 100644 --- a/docs/references/databases/get-index.md +++ b/docs/references/databases/get-index.md @@ -1 +1 @@ -Get index by ID. \ No newline at end of file +Get an index by its unique ID. \ No newline at end of file diff --git a/docs/references/databases/get-transaction.md b/docs/references/databases/get-transaction.md index e69de29bb2..41900f7468 100644 --- a/docs/references/databases/get-transaction.md +++ b/docs/references/databases/get-transaction.md @@ -0,0 +1 @@ +Get a transaction by its unique ID. \ No newline at end of file diff --git a/docs/references/databases/list-transactions.md b/docs/references/databases/list-transactions.md index e69de29bb2..9a63d9f04a 100644 --- a/docs/references/databases/list-transactions.md +++ b/docs/references/databases/list-transactions.md @@ -0,0 +1 @@ +List transactions across all databases. \ No newline at end of file diff --git a/docs/references/databases/update-transaction.md b/docs/references/databases/update-transaction.md index e69de29bb2..d9d5f45439 100644 --- a/docs/references/databases/update-transaction.md +++ b/docs/references/databases/update-transaction.md @@ -0,0 +1 @@ +Update a transaction, to either commit or roll back its operations. \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index 77589e848a..4a7e95d5ca 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -40,7 +40,7 @@ class Create extends Action group: 'transactions', name: 'createTransaction', description: '/docs/references/databases/create-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_CREATED, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php index 81315498f6..43c0c5c4a8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php @@ -39,7 +39,7 @@ class Delete extends Action group: 'transactions', name: 'deleteTransaction', description: '/docs/references/databases/delete-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_NOCONTENT, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php index 6afe28d626..43406a6751 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php @@ -38,7 +38,7 @@ class Get extends Action group: 'transactions', name: 'getTransaction', description: '/docs/references/databases/get-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index dde39efd90..ed70afee35 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -48,7 +48,7 @@ class Update extends Action group: 'transactions', name: 'updateTransaction', description: '/docs/references/databases/update-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php index b13ca8c52b..05ef5ae9e8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php @@ -41,7 +41,7 @@ class XList extends Action group: 'transactions', name: 'listTransactions', description: '/docs/references/databases/list-transactions.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, From 6652e27dd59bec080f8382ad462bd1fafb410ea9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 22:21:53 +1200 Subject: [PATCH 142/385] Update TablesDB auth --- .../Modules/Databases/Http/Databases/Transactions/Action.php | 1 - .../Modules/Databases/Http/TablesDB/Transactions/Create.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Delete.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Get.php | 2 +- .../Databases/Http/TablesDB/Transactions/Operations/Create.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Update.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/XList.php | 2 +- 7 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php index 512deb8fc8..8915ae6141 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php @@ -2,7 +2,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; -use Appwrite\Extend\Exception; use Utopia\Platform\Action as UtopiaAction; abstract class Action extends UtopiaAction diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php index 77b87c77dc..6a28d31621 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php @@ -37,7 +37,7 @@ class Create extends TransactionsCreate group: 'transactions', name: 'createTransaction', description: '/docs/references/tablesdb/create-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_CREATED, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php index 9440b586c5..cad11e795a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php @@ -37,7 +37,7 @@ class Delete extends TransactionsDelete group: 'transactions', name: 'deleteTransaction', description: '/docs/references/tablesdb/delete-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_NOCONTENT, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php index a5e9c374a6..39f6754a1e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php @@ -37,7 +37,7 @@ class Get extends TransactionsGet group: 'transactions', name: 'getTransaction', description: '/docs/references/tablesdb/get-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index eac524682c..6b2ee2ce4c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -39,7 +39,7 @@ class Create extends OperationsCreate group: 'transactions', name: 'createOperations', description: '/docs/references/tablesdb/create-operations.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_CREATED, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index d5b0e737a0..b754ec97a1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -38,7 +38,7 @@ class Update extends TransactionsUpdate group: 'transactions', name: 'updateTransaction', description: '/docs/references/tablesdb/update-transaction.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php index e04d28f200..67a58d7e5f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php @@ -37,7 +37,7 @@ class XList extends TransactionsList group: 'transactions', name: 'listTransactions', description: '/docs/references/tablesdb/list-transactions.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_OK, From b74e5ce5cd707817b1ccd201ca4d548a16f02af6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 22:22:46 +1200 Subject: [PATCH 143/385] Block bulk ops through txn create multi ops --- .../Databases/Transactions/Operations/Create.php | 15 ++++++++++++++- .../Utopia/Database/Validator/Operation.php | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 58505591ff..a33af05e3d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Operations; +use Appwrite\Auth\Auth; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Action; use Appwrite\SDK\AuthType; @@ -13,6 +14,7 @@ use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\ArrayList; @@ -43,7 +45,7 @@ class Create extends Action group: 'transactions', name: 'createOperations', description: '/docs/references/databases/create-operations.md', - auth: [AuthType::KEY], + auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_CREATED, @@ -77,8 +79,19 @@ class Create extends Action ); } + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $databases = $collections = $staged = []; foreach ($operations as $operation) { + if (!$isAPIKey && !$isPrivilegedUser && \in_array($operation['action'], [ + 'bulkCreate', + 'bulkUpdate', + 'bulkDelete' + ])) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + $database = $databases[$operation['databaseId']] ??= $dbForProject->getDocument('databases', $operation['databaseId']); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index ec2d4f0d49..f3fe2375b0 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -64,6 +64,16 @@ class Operation extends Validator return $this->description; } + public function getCollectionIdName(): string + { + return $this->collectionIdName; + } + + public function getDocumentIdName(): string + { + return $this->documentIdName; + } + public function isArray(): bool { return true; From 67b05a3f1ace8c333cf066061b54658631c6cb6e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 23:08:23 +1200 Subject: [PATCH 144/385] Fix test workflow --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7b1a243da1..cebdc02163 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -248,7 +248,6 @@ jobs: Console, Databases/Legacy, Databases/TablesDB, - Databases/Transactions, Functions, FunctionsSchedule, GraphQL, From fd6ddf6926bab320fc80e5f2edc8c426af290ec6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 11 Sep 2025 23:31:53 +1200 Subject: [PATCH 145/385] Fix operation spec gen --- app/config/specs/open-api3-1.8.x-client.json | 1081 ++++++++++++- app/config/specs/open-api3-1.8.x-console.json | 1405 +++++++++++++++-- app/config/specs/open-api3-1.8.x-server.json | 1366 ++++++++++++++-- app/config/specs/open-api3-latest-client.json | 1081 ++++++++++++- .../specs/open-api3-latest-console.json | 1405 +++++++++++++++-- app/config/specs/open-api3-latest-server.json | 1366 ++++++++++++++-- app/config/specs/swagger2-1.8.x-client.json | 1064 ++++++++++++- app/config/specs/swagger2-1.8.x-console.json | 1374 ++++++++++++++-- app/config/specs/swagger2-1.8.x-server.json | 1340 ++++++++++++++-- app/config/specs/swagger2-latest-client.json | 1064 ++++++++++++- app/config/specs/swagger2-latest-console.json | 1374 ++++++++++++++-- app/config/specs/swagger2-latest-server.json | 1340 ++++++++++++++-- .../SDK/Specification/Format/OpenAPI3.php | 95 +- .../SDK/Specification/Format/Swagger2.php | 73 +- .../Utopia/Database/Validator/Operation.php | 4 +- 15 files changed, 14213 insertions(+), 1219 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index d226fcc4e1..e94ac895b6 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -4806,6 +4806,424 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents": { "get": { "summary": "List documents", @@ -4893,6 +5311,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -4951,7 +5379,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5037,6 +5466,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5142,6 +5576,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -5200,7 +5644,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5283,6 +5728,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -5396,6 +5846,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5480,7 +5935,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/{attribute}\/decrement": { @@ -5594,6 +6065,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5713,6 +6189,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5745,7 +6226,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -5820,7 +6301,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -5896,9 +6377,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -5936,7 +6417,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -7467,6 +7948,424 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows": { "get": { "summary": "List rows", @@ -7491,7 +8390,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -7553,6 +8452,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -7579,7 +8488,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -7610,7 +8519,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7692,6 +8602,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -7724,7 +8639,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -7796,6 +8711,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -7822,7 +8747,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -7853,7 +8778,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7931,6 +8857,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -7961,7 +8892,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -8040,6 +8971,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -8063,7 +8999,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -8123,7 +9059,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/{column}\/decrement": { @@ -8150,7 +9102,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -8236,6 +9188,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -8268,7 +9225,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -8354,6 +9311,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9935,6 +10897,34 @@ "localeCodes": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "row": { "description": "Row", "type": "object", @@ -11839,6 +12829,59 @@ "recoveryCode": true } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 02d97fffc7..1ff651107b 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -4888,7 +4888,7 @@ "x-appwrite": { "method": "getResource", "group": null, - "weight": 496, + "weight": 508, "cookies": false, "type": "", "demo": "console\/get-resource.md", @@ -5205,6 +5205,424 @@ } } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/usage": { "get": { "summary": "Get databases usage stats", @@ -9460,6 +9878,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -9518,7 +9946,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9549,7 +9978,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9634,6 +10064,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9693,7 +10128,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9759,6 +10195,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9860,6 +10301,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9953,6 +10399,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10058,6 +10509,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -10116,7 +10577,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -10199,6 +10661,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -10312,6 +10779,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10396,7 +10868,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/logs": { @@ -10607,6 +11095,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10726,6 +11219,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10960,7 +11458,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -11540,7 +12038,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -11613,7 +12111,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -11846,7 +12344,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -11895,7 +12393,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -11945,7 +12443,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 466, + "weight": 478, "cookies": false, "type": "", "demo": "functions\/list-templates.md", @@ -12045,7 +12543,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 465, + "weight": 477, "cookies": false, "type": "", "demo": "functions\/get-template.md", @@ -12105,7 +12603,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 459, + "weight": 471, "cookies": false, "type": "", "demo": "functions\/list-usage.md", @@ -12177,7 +12675,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -12236,7 +12734,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -12466,7 +12964,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -12527,7 +13025,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -12607,7 +13105,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -12690,7 +13188,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -12786,7 +13284,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -12871,7 +13369,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -12974,7 +13472,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -13071,7 +13569,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -13133,7 +13631,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -13197,7 +13695,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -13287,7 +13785,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -13358,7 +13856,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -13433,7 +13931,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -13509,9 +14007,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -13549,7 +14047,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -13614,7 +14112,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -13685,7 +14183,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 458, + "weight": 470, "cookies": false, "type": "", "demo": "functions\/get-usage.md", @@ -13767,7 +14265,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -13826,7 +14324,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -13917,7 +14415,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -13986,7 +14484,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -14077,7 +14575,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -16411,7 +16909,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -16592,7 +17090,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -21190,7 +21688,7 @@ "resourceId": { "type": "string", "description": "Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "internalFile": { "type": "boolean", @@ -22433,7 +22931,7 @@ "x-appwrite": { "method": "list", "group": "projects", - "weight": 436, + "weight": 448, "cookies": false, "type": "", "demo": "projects\/list.md", @@ -24068,7 +24566,7 @@ "x-appwrite": { "method": "listDevKeys", "group": "devKeys", - "weight": 434, + "weight": 446, "cookies": false, "type": "", "demo": "projects\/list-dev-keys.md", @@ -24106,7 +24604,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: accessedAt, expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -24136,7 +24637,7 @@ "x-appwrite": { "method": "createDevKey", "group": "devKeys", - "weight": 431, + "weight": 443, "cookies": false, "type": "", "demo": "projects\/create-dev-key.md", @@ -24221,7 +24722,7 @@ "x-appwrite": { "method": "getDevKey", "group": "devKeys", - "weight": 433, + "weight": 445, "cookies": false, "type": "", "demo": "projects\/get-dev-key.md", @@ -24289,7 +24790,7 @@ "x-appwrite": { "method": "updateDevKey", "group": "devKeys", - "weight": 432, + "weight": 444, "cookies": false, "type": "", "demo": "projects\/update-dev-key.md", @@ -24375,7 +24876,7 @@ "x-appwrite": { "method": "deleteDevKey", "group": "devKeys", - "weight": 435, + "weight": 447, "cookies": false, "type": "", "demo": "projects\/delete-dev-key.md", @@ -28209,7 +28710,7 @@ "x-appwrite": { "method": "listRules", "group": null, - "weight": 502, + "weight": 514, "cookies": false, "type": "", "demo": "proxy\/list-rules.md", @@ -28283,7 +28784,7 @@ "x-appwrite": { "method": "createAPIRule", "group": null, - "weight": 497, + "weight": 509, "cookies": false, "type": "", "demo": "proxy\/create-api-rule.md", @@ -28350,7 +28851,7 @@ "x-appwrite": { "method": "createFunctionRule", "group": null, - "weight": 499, + "weight": 511, "cookies": false, "type": "", "demo": "proxy\/create-function-rule.md", @@ -28428,7 +28929,7 @@ "x-appwrite": { "method": "createRedirectRule", "group": null, - "weight": 500, + "weight": 512, "cookies": false, "type": "", "demo": "proxy\/create-redirect-rule.md", @@ -28541,7 +29042,7 @@ "x-appwrite": { "method": "createSiteRule", "group": null, - "weight": 498, + "weight": 510, "cookies": false, "type": "", "demo": "proxy\/create-site-rule.md", @@ -28619,7 +29120,7 @@ "x-appwrite": { "method": "getRule", "group": null, - "weight": 501, + "weight": 513, "cookies": false, "type": "", "demo": "proxy\/get-rule.md", @@ -28670,7 +29171,7 @@ "x-appwrite": { "method": "deleteRule", "group": null, - "weight": 503, + "weight": 515, "cookies": false, "type": "", "demo": "proxy\/delete-rule.md", @@ -28730,7 +29231,7 @@ "x-appwrite": { "method": "updateRuleVerification", "group": null, - "weight": 504, + "weight": 516, "cookies": false, "type": "", "demo": "proxy\/update-rule-verification.md", @@ -28790,7 +29291,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -28819,7 +29320,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, enabled, framework, deploymentId, buildCommand, installCommand, outputDirectory, installationId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -28860,7 +29364,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -29109,7 +29613,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -29158,7 +29662,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -29208,7 +29712,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 491, + "weight": 503, "cookies": false, "type": "", "demo": "sites\/list-templates.md", @@ -29308,7 +29812,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 492, + "weight": 504, "cookies": false, "type": "", "demo": "sites\/get-template.md", @@ -29368,7 +29872,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 493, + "weight": 505, "cookies": false, "type": "", "demo": "sites\/list-usage.md", @@ -29440,7 +29944,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -29499,7 +30003,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -29744,7 +30248,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -29805,7 +30309,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -29885,7 +30389,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -29968,7 +30472,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -30069,7 +30573,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -30149,7 +30653,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -30252,7 +30756,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -30350,7 +30854,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -30412,7 +30916,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -30476,7 +30980,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -30566,7 +31070,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -30637,7 +31141,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -30676,7 +31180,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: trigger, status, responseStatusCode, duration, requestMethod, requestPath, deploymentId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -30708,7 +31215,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -30770,7 +31277,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -30841,7 +31348,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 494, + "weight": 506, "cookies": false, "type": "", "demo": "sites\/get-usage.md", @@ -30923,7 +31430,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -30982,7 +31489,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -31073,7 +31580,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -31142,7 +31649,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -31233,7 +31740,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -32705,7 +33212,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -32778,7 +33285,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -32833,6 +33340,424 @@ } } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/usage": { "get": { "summary": "Get TablesDB usage stats", @@ -32857,7 +33782,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 378, + "weight": 384, "cookies": false, "type": "", "demo": "tablesdb\/list-usage.md", @@ -32954,7 +33879,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -33013,7 +33938,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -33089,7 +34014,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -33150,7 +34075,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -33236,7 +34161,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -33343,7 +34268,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -33415,7 +34340,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -33517,7 +34442,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -33591,7 +34516,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -33678,7 +34603,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -33787,7 +34712,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -33901,7 +34826,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -34010,7 +34935,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -34124,7 +35049,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -34233,7 +35158,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -34347,7 +35272,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -34465,7 +35390,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -34588,7 +35513,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -34707,7 +35632,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -34831,7 +35756,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -34950,7 +35875,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -35074,7 +35999,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -35183,7 +36108,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -35297,7 +36222,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -35409,7 +36334,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -35529,7 +36454,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -35641,7 +36566,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -35761,7 +36686,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -35873,7 +36798,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -35993,7 +36918,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -36127,7 +37052,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -36247,7 +37172,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -36366,7 +37291,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -36475,7 +37400,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -36620,7 +37545,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -36694,7 +37619,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -36777,7 +37702,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -36888,7 +37813,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -36973,7 +37898,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -37105,7 +38030,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -37179,7 +38104,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -37262,7 +38187,7 @@ "x-appwrite": { "method": "listTableLogs", "group": "tables", - "weight": 384, + "weight": 390, "cookies": false, "type": "", "demo": "tablesdb\/list-table-logs.md", @@ -37348,7 +38273,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -37410,6 +38335,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -37436,7 +38371,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -37467,7 +38402,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -37494,7 +38430,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37575,6 +38512,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37605,7 +38547,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -37633,7 +38575,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37695,6 +38638,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -37728,7 +38676,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -37795,6 +38743,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37825,7 +38778,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -37887,6 +38840,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37919,7 +38877,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -37991,6 +38949,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -38017,7 +38985,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -38048,7 +39016,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -38126,6 +39095,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38156,7 +39130,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -38235,6 +39209,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38258,7 +39237,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -38318,7 +39297,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/logs": { @@ -38345,7 +39340,7 @@ "x-appwrite": { "method": "listRowLogs", "group": "logs", - "weight": 428, + "weight": 434, "cookies": false, "type": "", "demo": "tablesdb\/list-row-logs.md", @@ -38441,7 +39436,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -38527,6 +39522,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38559,7 +39559,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -38645,6 +39645,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38677,7 +39682,7 @@ "x-appwrite": { "method": "getTableUsage", "group": null, - "weight": 385, + "weight": 391, "cookies": false, "type": "", "demo": "tablesdb\/get-table-usage.md", @@ -38772,7 +39777,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 377, + "weight": 383, "cookies": false, "type": "", "demo": "tablesdb\/get-usage.md", @@ -39984,7 +40989,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -40034,7 +41039,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -40064,7 +41072,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -40153,7 +41161,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -40213,7 +41221,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -40283,7 +41291,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -46107,6 +47115,34 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "migrationList": { "description": "Migrations List", "type": "object", @@ -56505,6 +57541,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 09d53dbdf0..987ae7fece 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -4743,6 +4743,436 @@ } } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/{databaseId}": { "get": { "summary": "Get database", @@ -8938,6 +9368,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -8997,7 +9437,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9029,7 +9470,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9116,6 +9558,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9176,7 +9623,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9243,6 +9691,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9345,6 +9798,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9439,6 +9897,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9546,6 +10009,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -9605,7 +10078,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9690,6 +10164,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9805,6 +10284,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9891,7 +10375,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/{attribute}\/decrement": { @@ -10007,6 +10507,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10128,6 +10633,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10364,7 +10874,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -10542,7 +11052,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -10616,7 +11126,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -10850,7 +11360,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -10900,7 +11410,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -10951,7 +11461,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -11011,7 +11521,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -11242,7 +11752,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -11304,7 +11814,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -11385,7 +11895,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -11469,7 +11979,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -11566,7 +12076,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -11652,7 +12162,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -11756,7 +12266,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -11854,7 +12364,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -11917,7 +12427,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -11982,7 +12492,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -12073,7 +12583,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -12145,7 +12655,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -12222,7 +12732,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -12300,9 +12810,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -12340,7 +12850,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -12407,7 +12917,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -12479,7 +12989,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -12539,7 +13049,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -12631,7 +13141,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -12701,7 +13211,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -12793,7 +13303,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -15174,7 +15684,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -15356,7 +15866,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -19717,7 +20227,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -19747,7 +20257,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, enabled, framework, deploymentId, buildCommand, installCommand, outputDirectory, installationId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -19788,7 +20301,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -20038,7 +20551,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -20088,7 +20601,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -20139,7 +20652,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -20199,7 +20712,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -20445,7 +20958,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -20507,7 +21020,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -20588,7 +21101,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -20672,7 +21185,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -20774,7 +21287,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -20855,7 +21368,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -20959,7 +21472,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -21058,7 +21571,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -21121,7 +21634,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -21186,7 +21699,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -21277,7 +21790,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -21349,7 +21862,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -21389,7 +21902,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: trigger, status, responseStatusCode, duration, requestMethod, requestPath, deploymentId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -21421,7 +21937,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -21484,7 +22000,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -21556,7 +22072,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -21616,7 +22132,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -21708,7 +22224,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -21778,7 +22294,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -21870,7 +22386,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -23210,7 +23726,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -23284,7 +23800,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -23340,6 +23856,436 @@ } } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/{databaseId}": { "get": { "summary": "Get database", @@ -23364,7 +24310,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -23424,7 +24370,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -23501,7 +24447,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -23563,7 +24509,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -23650,7 +24596,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -23758,7 +24704,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -23831,7 +24777,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -23934,7 +24880,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -24009,7 +24955,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -24097,7 +25043,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -24207,7 +25153,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -24322,7 +25268,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -24432,7 +25378,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -24547,7 +25493,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -24657,7 +25603,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -24772,7 +25718,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -24891,7 +25837,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -25015,7 +25961,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -25135,7 +26081,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -25260,7 +26206,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -25380,7 +26326,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -25505,7 +26451,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -25615,7 +26561,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -25730,7 +26676,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -25843,7 +26789,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -25964,7 +26910,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -26077,7 +27023,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -26198,7 +27144,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -26311,7 +27257,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -26432,7 +27378,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -26567,7 +27513,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -26688,7 +27634,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -26808,7 +27754,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -26918,7 +27864,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -27064,7 +28010,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -27139,7 +28085,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -27223,7 +28169,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -27335,7 +28281,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -27421,7 +28367,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -27554,7 +28500,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -27629,7 +28575,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -27713,7 +28659,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -27777,6 +28723,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -27803,7 +28759,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -27835,7 +28791,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -27863,7 +28820,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -27946,6 +28904,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -27976,7 +28939,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -28005,7 +28968,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28068,6 +29032,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -28101,7 +29070,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -28169,6 +29138,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28199,7 +29173,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -28262,6 +29236,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28294,7 +29273,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -28368,6 +29347,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -28394,7 +29383,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -28426,7 +29415,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -28506,6 +29496,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28536,7 +29531,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -28617,6 +29612,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28640,7 +29640,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -28702,7 +29702,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/{column}\/decrement": { @@ -28729,7 +29745,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -28817,6 +29833,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28849,7 +29870,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -28937,6 +29958,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -30024,7 +31050,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -30075,7 +31101,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -30105,7 +31134,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -30195,7 +31224,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -30256,7 +31285,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -30327,7 +31356,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -35033,6 +36062,34 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "specificationList": { "description": "Specifications List", "type": "object", @@ -41438,6 +42495,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index d226fcc4e1..a24d36fe76 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -4806,6 +4806,424 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents": { "get": { "summary": "List documents", @@ -4893,6 +5311,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -4951,7 +5379,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5037,6 +5466,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5142,6 +5576,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -5200,7 +5644,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5283,6 +5728,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -5396,6 +5846,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5480,7 +5935,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/{attribute}\/decrement": { @@ -5594,6 +6065,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5713,6 +6189,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -5745,7 +6226,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -5820,7 +6301,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -5896,9 +6377,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -5936,7 +6417,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -7467,6 +7948,424 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows": { "get": { "summary": "List rows", @@ -7491,7 +8390,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -7553,6 +8452,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -7579,7 +8488,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -7610,7 +8519,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7692,6 +8602,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -7724,7 +8639,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -7796,6 +8711,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -7822,7 +8747,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -7853,7 +8778,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7931,6 +8857,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -7961,7 +8892,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -8040,6 +8971,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -8063,7 +8999,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -8123,7 +9059,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/{column}\/decrement": { @@ -8150,7 +9102,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -8236,6 +9188,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -8268,7 +9225,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -8354,6 +9311,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9935,6 +10897,34 @@ "localeCodes": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "row": { "description": "Row", "type": "object", @@ -11839,6 +12829,59 @@ "recoveryCode": true } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 02d97fffc7..3316be2f8b 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -4888,7 +4888,7 @@ "x-appwrite": { "method": "getResource", "group": null, - "weight": 496, + "weight": 508, "cookies": false, "type": "", "demo": "console\/get-resource.md", @@ -5205,6 +5205,424 @@ } } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/usage": { "get": { "summary": "Get databases usage stats", @@ -9460,6 +9878,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -9518,7 +9946,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9549,7 +9978,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9634,6 +10064,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9693,7 +10128,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9759,6 +10195,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9860,6 +10301,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9953,6 +10399,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10058,6 +10509,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -10116,7 +10577,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -10199,6 +10661,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -10312,6 +10779,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10396,7 +10868,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/logs": { @@ -10607,6 +11095,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10726,6 +11219,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10960,7 +11458,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -11540,7 +12038,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -11613,7 +12111,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -11846,7 +12344,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -11895,7 +12393,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -11945,7 +12443,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 466, + "weight": 478, "cookies": false, "type": "", "demo": "functions\/list-templates.md", @@ -12045,7 +12543,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 465, + "weight": 477, "cookies": false, "type": "", "demo": "functions\/get-template.md", @@ -12105,7 +12603,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 459, + "weight": 471, "cookies": false, "type": "", "demo": "functions\/list-usage.md", @@ -12177,7 +12675,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -12236,7 +12734,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -12466,7 +12964,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -12527,7 +13025,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -12607,7 +13105,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -12690,7 +13188,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -12786,7 +13284,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -12871,7 +13369,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -12974,7 +13472,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -13071,7 +13569,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -13133,7 +13631,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -13197,7 +13695,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -13287,7 +13785,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -13358,7 +13856,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -13433,7 +13931,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -13509,9 +14007,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -13549,7 +14047,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -13614,7 +14112,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -13685,7 +14183,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 458, + "weight": 470, "cookies": false, "type": "", "demo": "functions\/get-usage.md", @@ -13767,7 +14265,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -13826,7 +14324,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -13917,7 +14415,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -13986,7 +14484,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -14077,7 +14575,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -16411,7 +16909,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -16592,7 +17090,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -21190,7 +21688,7 @@ "resourceId": { "type": "string", "description": "Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "internalFile": { "type": "boolean", @@ -22433,7 +22931,7 @@ "x-appwrite": { "method": "list", "group": "projects", - "weight": 436, + "weight": 448, "cookies": false, "type": "", "demo": "projects\/list.md", @@ -24068,7 +24566,7 @@ "x-appwrite": { "method": "listDevKeys", "group": "devKeys", - "weight": 434, + "weight": 446, "cookies": false, "type": "", "demo": "projects\/list-dev-keys.md", @@ -24106,7 +24604,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: accessedAt, expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -24136,7 +24637,7 @@ "x-appwrite": { "method": "createDevKey", "group": "devKeys", - "weight": 431, + "weight": 443, "cookies": false, "type": "", "demo": "projects\/create-dev-key.md", @@ -24221,7 +24722,7 @@ "x-appwrite": { "method": "getDevKey", "group": "devKeys", - "weight": 433, + "weight": 445, "cookies": false, "type": "", "demo": "projects\/get-dev-key.md", @@ -24289,7 +24790,7 @@ "x-appwrite": { "method": "updateDevKey", "group": "devKeys", - "weight": 432, + "weight": 444, "cookies": false, "type": "", "demo": "projects\/update-dev-key.md", @@ -24375,7 +24876,7 @@ "x-appwrite": { "method": "deleteDevKey", "group": "devKeys", - "weight": 435, + "weight": 447, "cookies": false, "type": "", "demo": "projects\/delete-dev-key.md", @@ -28209,7 +28710,7 @@ "x-appwrite": { "method": "listRules", "group": null, - "weight": 502, + "weight": 514, "cookies": false, "type": "", "demo": "proxy\/list-rules.md", @@ -28283,7 +28784,7 @@ "x-appwrite": { "method": "createAPIRule", "group": null, - "weight": 497, + "weight": 509, "cookies": false, "type": "", "demo": "proxy\/create-api-rule.md", @@ -28350,7 +28851,7 @@ "x-appwrite": { "method": "createFunctionRule", "group": null, - "weight": 499, + "weight": 511, "cookies": false, "type": "", "demo": "proxy\/create-function-rule.md", @@ -28428,7 +28929,7 @@ "x-appwrite": { "method": "createRedirectRule", "group": null, - "weight": 500, + "weight": 512, "cookies": false, "type": "", "demo": "proxy\/create-redirect-rule.md", @@ -28541,7 +29042,7 @@ "x-appwrite": { "method": "createSiteRule", "group": null, - "weight": 498, + "weight": 510, "cookies": false, "type": "", "demo": "proxy\/create-site-rule.md", @@ -28619,7 +29120,7 @@ "x-appwrite": { "method": "getRule", "group": null, - "weight": 501, + "weight": 513, "cookies": false, "type": "", "demo": "proxy\/get-rule.md", @@ -28670,7 +29171,7 @@ "x-appwrite": { "method": "deleteRule", "group": null, - "weight": 503, + "weight": 515, "cookies": false, "type": "", "demo": "proxy\/delete-rule.md", @@ -28730,7 +29231,7 @@ "x-appwrite": { "method": "updateRuleVerification", "group": null, - "weight": 504, + "weight": 516, "cookies": false, "type": "", "demo": "proxy\/update-rule-verification.md", @@ -28790,7 +29291,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -28819,7 +29320,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, enabled, framework, deploymentId, buildCommand, installCommand, outputDirectory, installationId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -28860,7 +29364,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -29109,7 +29613,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -29158,7 +29662,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -29208,7 +29712,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 491, + "weight": 503, "cookies": false, "type": "", "demo": "sites\/list-templates.md", @@ -29308,7 +29812,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 492, + "weight": 504, "cookies": false, "type": "", "demo": "sites\/get-template.md", @@ -29368,7 +29872,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 493, + "weight": 505, "cookies": false, "type": "", "demo": "sites\/list-usage.md", @@ -29440,7 +29944,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -29499,7 +30003,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -29744,7 +30248,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -29805,7 +30309,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -29885,7 +30389,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -29968,7 +30472,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -30069,7 +30573,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -30149,7 +30653,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -30252,7 +30756,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -30350,7 +30854,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -30412,7 +30916,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -30476,7 +30980,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -30566,7 +31070,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -30637,7 +31141,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -30676,7 +31180,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: trigger, status, responseStatusCode, duration, requestMethod, requestPath, deploymentId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -30708,7 +31215,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -30770,7 +31277,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -30841,7 +31348,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 494, + "weight": 506, "cookies": false, "type": "", "demo": "sites\/get-usage.md", @@ -30923,7 +31430,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -30982,7 +31489,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -31073,7 +31580,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -31142,7 +31649,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -31233,7 +31740,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -32705,7 +33212,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -32778,7 +33285,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -32833,6 +33340,424 @@ } } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/usage": { "get": { "summary": "Get TablesDB usage stats", @@ -32857,7 +33782,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 378, + "weight": 384, "cookies": false, "type": "", "demo": "tablesdb\/list-usage.md", @@ -32954,7 +33879,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -33013,7 +33938,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -33089,7 +34014,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -33150,7 +34075,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -33236,7 +34161,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -33343,7 +34268,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -33415,7 +34340,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -33517,7 +34442,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -33591,7 +34516,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -33678,7 +34603,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -33787,7 +34712,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -33901,7 +34826,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -34010,7 +34935,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -34124,7 +35049,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -34233,7 +35158,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -34347,7 +35272,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -34465,7 +35390,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -34588,7 +35513,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -34707,7 +35632,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -34831,7 +35756,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -34950,7 +35875,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -35074,7 +35999,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -35183,7 +36108,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -35297,7 +36222,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -35409,7 +36334,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -35529,7 +36454,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -35641,7 +36566,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -35761,7 +36686,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -35873,7 +36798,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -35993,7 +36918,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -36127,7 +37052,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -36247,7 +37172,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -36366,7 +37291,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -36475,7 +37400,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -36620,7 +37545,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -36694,7 +37619,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -36777,7 +37702,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -36888,7 +37813,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -36973,7 +37898,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -37105,7 +38030,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -37179,7 +38104,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -37262,7 +38187,7 @@ "x-appwrite": { "method": "listTableLogs", "group": "tables", - "weight": 384, + "weight": 390, "cookies": false, "type": "", "demo": "tablesdb\/list-table-logs.md", @@ -37348,7 +38273,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -37410,6 +38335,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -37436,7 +38371,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -37467,7 +38402,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -37494,7 +38430,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37575,6 +38512,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37605,7 +38547,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -37633,7 +38575,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37695,6 +38638,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -37728,7 +38676,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -37795,6 +38743,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37825,7 +38778,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -37887,6 +38840,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -37919,7 +38877,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -37991,6 +38949,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -38017,7 +38985,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -38048,7 +39016,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -38126,6 +39095,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38156,7 +39130,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -38235,6 +39209,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38258,7 +39237,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -38318,7 +39297,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/logs": { @@ -38345,7 +39340,7 @@ "x-appwrite": { "method": "listRowLogs", "group": "logs", - "weight": 428, + "weight": 434, "cookies": false, "type": "", "demo": "tablesdb\/list-row-logs.md", @@ -38441,7 +39436,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -38527,6 +39522,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38559,7 +39559,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -38645,6 +39645,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -38677,7 +39682,7 @@ "x-appwrite": { "method": "getTableUsage", "group": null, - "weight": 385, + "weight": 391, "cookies": false, "type": "", "demo": "tablesdb\/get-table-usage.md", @@ -38772,7 +39777,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 377, + "weight": 383, "cookies": false, "type": "", "demo": "tablesdb\/get-usage.md", @@ -39984,7 +40989,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -40034,7 +41039,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -40064,7 +41072,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -40153,7 +41161,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -40213,7 +41221,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -40283,7 +41291,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -46107,6 +47115,34 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "migrationList": { "description": "Migrations List", "type": "object", @@ -56505,6 +57541,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 09d53dbdf0..a2d91def99 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -4743,6 +4743,436 @@ } } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/databases\/{databaseId}": { "get": { "summary": "Get database", @@ -8938,6 +9368,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -8997,7 +9437,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9029,7 +9470,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9116,6 +9558,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9176,7 +9623,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9243,6 +9691,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9345,6 +9798,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9439,6 +9897,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9546,6 +10009,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -9605,7 +10078,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9690,6 +10164,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -9805,6 +10284,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -9891,7 +10375,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}\/{attribute}\/decrement": { @@ -10007,6 +10507,11 @@ "type": "number", "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10128,6 +10633,11 @@ "type": "number", "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -10364,7 +10874,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -10542,7 +11052,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -10616,7 +11126,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -10850,7 +11360,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -10900,7 +11410,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -10951,7 +11461,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -11011,7 +11521,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -11242,7 +11752,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -11304,7 +11814,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -11385,7 +11895,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -11469,7 +11979,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -11566,7 +12076,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -11652,7 +12162,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -11756,7 +12266,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -11854,7 +12364,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -11917,7 +12427,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -11982,7 +12492,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -12073,7 +12583,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -12145,7 +12655,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -12222,7 +12732,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -12300,9 +12810,9 @@ "x-enum-keys": [] }, "headers": { - "type": "string", + "type": "object", "description": "HTTP headers of execution. Defaults to empty.", - "x-example": null + "x-example": "{}" }, "scheduledAt": { "type": "string", @@ -12340,7 +12850,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -12407,7 +12917,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -12479,7 +12989,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -12539,7 +13049,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -12631,7 +13141,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -12701,7 +13211,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -12793,7 +13303,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -15174,7 +15684,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -15356,7 +15866,7 @@ "image": { "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -19717,7 +20227,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -19747,7 +20257,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, enabled, framework, deploymentId, buildCommand, installCommand, outputDirectory, installationId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -19788,7 +20301,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -20038,7 +20551,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -20088,7 +20601,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -20139,7 +20652,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -20199,7 +20712,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -20445,7 +20958,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -20507,7 +21020,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -20588,7 +21101,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -20672,7 +21185,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -20774,7 +21287,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -20855,7 +21368,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -20959,7 +21472,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -21058,7 +21571,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -21121,7 +21634,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -21186,7 +21699,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -21277,7 +21790,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -21349,7 +21862,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -21389,7 +21902,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: trigger, status, responseStatusCode, duration, requestMethod, requestPath, deploymentId", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -21421,7 +21937,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -21484,7 +22000,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -21556,7 +22072,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -21616,7 +22132,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -21708,7 +22224,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -21778,7 +22294,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -21870,7 +22386,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -23210,7 +23726,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -23284,7 +23800,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -23340,6 +23856,436 @@ } } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transactionList" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "x-example": 60 + } + } + } + } + } + } + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "x-example": false + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/transaction" + } + } + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + }, "\/tablesdb\/{databaseId}": { "get": { "summary": "Get database", @@ -23364,7 +24310,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -23424,7 +24370,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -23501,7 +24447,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -23563,7 +24509,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -23650,7 +24596,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -23758,7 +24704,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -23831,7 +24777,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -23934,7 +24880,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -24009,7 +24955,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -24097,7 +25043,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -24207,7 +25153,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -24322,7 +25268,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -24432,7 +25378,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -24547,7 +25493,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -24657,7 +25603,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -24772,7 +25718,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -24891,7 +25837,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -25015,7 +25961,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -25135,7 +26081,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -25260,7 +26206,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -25380,7 +26326,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -25505,7 +26451,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -25615,7 +26561,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -25730,7 +26676,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -25843,7 +26789,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -25964,7 +26910,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -26077,7 +27023,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -26198,7 +27144,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -26311,7 +27257,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -26432,7 +27378,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -26567,7 +27513,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -26688,7 +27634,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -26808,7 +27754,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -26918,7 +27864,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -27064,7 +28010,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -27139,7 +28085,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -27223,7 +28169,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -27335,7 +28281,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -27421,7 +28367,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -27554,7 +28500,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -27629,7 +28575,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -27713,7 +28659,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -27777,6 +28723,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -27803,7 +28759,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -27835,7 +28791,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -27863,7 +28820,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -27946,6 +28904,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -27976,7 +28939,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -28005,7 +28968,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28068,6 +29032,11 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } }, "required": [ @@ -28101,7 +29070,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -28169,6 +29138,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28199,7 +29173,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -28262,6 +29236,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28294,7 +29273,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -28368,6 +29347,16 @@ "default": [] }, "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "query" } ] }, @@ -28394,7 +29383,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -28426,7 +29415,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -28506,6 +29496,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28536,7 +29531,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -28617,6 +29612,11 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28640,7 +29640,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -28702,7 +29702,23 @@ }, "in": "path" } - ] + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" + } + } + } + } + } + } } }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows\/{rowId}\/{column}\/decrement": { @@ -28729,7 +29745,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -28817,6 +29833,11 @@ "type": "number", "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -28849,7 +29870,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -28937,6 +29958,11 @@ "type": "number", "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "x-example": "" } } } @@ -30024,7 +31050,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -30075,7 +31101,10 @@ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: expire", "required": false, "schema": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "default": [] }, "in": "query" @@ -30105,7 +31134,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -30195,7 +31224,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -30256,7 +31285,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -30327,7 +31356,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -35033,6 +36062,34 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "$ref": "#\/components\/schemas\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "specificationList": { "description": "Specifications List", "type": "object", @@ -41438,6 +42495,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 55911e9556..7297412924 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -4948,6 +4948,419 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents": { "get": { "summary": "List documents", @@ -5029,6 +5442,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -5088,7 +5509,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5173,6 +5595,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5269,6 +5697,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -5328,7 +5764,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5406,6 +5843,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -5514,6 +5957,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5593,6 +6042,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -5702,6 +6166,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5814,6 +6284,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5845,7 +6321,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -5918,7 +6394,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -6035,7 +6511,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -7549,6 +8025,419 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows": { "get": { "summary": "List rows", @@ -7573,7 +8462,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -7629,6 +8518,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -7657,7 +8554,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -7687,7 +8584,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7768,6 +8666,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -7799,7 +8703,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -7863,6 +8767,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -7891,7 +8803,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -7921,7 +8833,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7994,6 +8907,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8025,7 +8944,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -8098,6 +9017,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8124,7 +9049,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -8176,6 +9101,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -8206,7 +9146,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -8284,6 +9224,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8317,7 +9263,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -8395,6 +9341,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9931,6 +10883,35 @@ "localeCodes": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "row": { "description": "Row", "type": "object", @@ -11840,6 +12821,59 @@ "recoveryCode": true } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 6d5721c73b..5e8aad66e4 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -5052,7 +5052,7 @@ "x-appwrite": { "method": "getResource", "group": null, - "weight": 496, + "weight": 508, "cookies": false, "type": "", "demo": "console\/get-resource.md", @@ -5367,6 +5367,419 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/usage": { "get": { "summary": "Get databases usage stats", @@ -9525,6 +9938,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9584,7 +10005,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9615,7 +10037,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9699,6 +10122,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9759,7 +10188,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9821,6 +10251,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9920,6 +10356,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10010,6 +10452,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10106,6 +10554,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -10165,7 +10621,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -10243,6 +10700,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -10351,6 +10814,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10430,6 +10899,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -10629,6 +11113,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10741,6 +11231,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10974,7 +11470,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -11524,7 +12020,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -11596,7 +12092,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -11847,7 +12343,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -11896,7 +12392,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -11946,7 +12442,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 466, + "weight": 478, "cookies": false, "type": "", "demo": "functions\/list-templates.md", @@ -12040,7 +12536,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 465, + "weight": 477, "cookies": false, "type": "", "demo": "functions\/get-template.md", @@ -12098,7 +12594,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 459, + "weight": 471, "cookies": false, "type": "", "demo": "functions\/list-usage.md", @@ -12168,7 +12664,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -12227,7 +12723,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -12474,7 +12970,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -12535,7 +13031,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -12612,7 +13108,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -12692,7 +13188,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -12784,7 +13280,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -12869,7 +13365,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -12975,7 +13471,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -13071,7 +13567,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -13133,7 +13629,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -13200,7 +13696,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -13285,7 +13781,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -13352,7 +13848,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -13425,7 +13921,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -13542,7 +14038,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -13606,7 +14102,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -13673,7 +14169,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 458, + "weight": 470, "cookies": false, "type": "", "demo": "functions\/get-usage.md", @@ -13751,7 +14247,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -13810,7 +14306,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -13900,7 +14396,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -13967,7 +14463,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -14059,7 +14555,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -16423,7 +16919,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": "", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -16620,7 +17116,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -21355,7 +21851,7 @@ "type": "string", "description": "Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "internalFile": { "type": "boolean", @@ -22590,7 +23086,7 @@ "x-appwrite": { "method": "list", "group": "projects", - "weight": 436, + "weight": 448, "cookies": false, "type": "", "demo": "projects\/list.md", @@ -24233,7 +24729,7 @@ "x-appwrite": { "method": "listDevKeys", "group": "devKeys", - "weight": 434, + "weight": 446, "cookies": false, "type": "", "demo": "projects\/list-dev-keys.md", @@ -24303,7 +24799,7 @@ "x-appwrite": { "method": "createDevKey", "group": "devKeys", - "weight": 431, + "weight": 443, "cookies": false, "type": "", "demo": "projects\/create-dev-key.md", @@ -24386,7 +24882,7 @@ "x-appwrite": { "method": "getDevKey", "group": "devKeys", - "weight": 433, + "weight": 445, "cookies": false, "type": "", "demo": "projects\/get-dev-key.md", @@ -24452,7 +24948,7 @@ "x-appwrite": { "method": "updateDevKey", "group": "devKeys", - "weight": 432, + "weight": 444, "cookies": false, "type": "", "demo": "projects\/update-dev-key.md", @@ -24538,7 +25034,7 @@ "x-appwrite": { "method": "deleteDevKey", "group": "devKeys", - "weight": 435, + "weight": 447, "cookies": false, "type": "", "demo": "projects\/delete-dev-key.md", @@ -28351,7 +28847,7 @@ "x-appwrite": { "method": "listRules", "group": null, - "weight": 502, + "weight": 514, "cookies": false, "type": "", "demo": "proxy\/list-rules.md", @@ -28424,7 +28920,7 @@ "x-appwrite": { "method": "createAPIRule", "group": null, - "weight": 497, + "weight": 509, "cookies": false, "type": "", "demo": "proxy\/create-api-rule.md", @@ -28494,7 +28990,7 @@ "x-appwrite": { "method": "createFunctionRule", "group": null, - "weight": 499, + "weight": 511, "cookies": false, "type": "", "demo": "proxy\/create-function-rule.md", @@ -28577,7 +29073,7 @@ "x-appwrite": { "method": "createRedirectRule", "group": null, - "weight": 500, + "weight": 512, "cookies": false, "type": "", "demo": "proxy\/create-redirect-rule.md", @@ -28697,7 +29193,7 @@ "x-appwrite": { "method": "createSiteRule", "group": null, - "weight": 498, + "weight": 510, "cookies": false, "type": "", "demo": "proxy\/create-site-rule.md", @@ -28778,7 +29274,7 @@ "x-appwrite": { "method": "getRule", "group": null, - "weight": 501, + "weight": 513, "cookies": false, "type": "", "demo": "proxy\/get-rule.md", @@ -28831,7 +29327,7 @@ "x-appwrite": { "method": "deleteRule", "group": null, - "weight": 503, + "weight": 515, "cookies": false, "type": "", "demo": "proxy\/delete-rule.md", @@ -28891,7 +29387,7 @@ "x-appwrite": { "method": "updateRuleVerification", "group": null, - "weight": 504, + "weight": 516, "cookies": false, "type": "", "demo": "proxy\/update-rule-verification.md", @@ -28949,7 +29445,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -29021,7 +29517,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -29288,7 +29784,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -29337,7 +29833,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -29387,7 +29883,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 491, + "weight": 503, "cookies": false, "type": "", "demo": "sites\/list-templates.md", @@ -29481,7 +29977,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 492, + "weight": 504, "cookies": false, "type": "", "demo": "sites\/get-template.md", @@ -29539,7 +30035,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 493, + "weight": 505, "cookies": false, "type": "", "demo": "sites\/list-usage.md", @@ -29609,7 +30105,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -29668,7 +30164,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -29930,7 +30426,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -29991,7 +30487,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -30068,7 +30564,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -30148,7 +30644,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -30248,7 +30744,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -30327,7 +30823,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -30433,7 +30929,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -30530,7 +31026,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -30592,7 +31088,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -30659,7 +31155,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -30744,7 +31240,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -30811,7 +31307,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -30882,7 +31378,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -30946,7 +31442,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -31013,7 +31509,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 494, + "weight": 506, "cookies": false, "type": "", "demo": "sites\/get-usage.md", @@ -31091,7 +31587,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -31150,7 +31646,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -31240,7 +31736,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -31307,7 +31803,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -31399,7 +31895,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -32833,7 +33329,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -32905,7 +33401,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -32963,6 +33459,419 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/usage": { "get": { "summary": "Get TablesDB usage stats", @@ -32987,7 +33896,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 378, + "weight": 384, "cookies": false, "type": "", "demo": "tablesdb\/list-usage.md", @@ -33082,7 +33991,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -33141,7 +34050,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -33219,7 +34128,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -33278,7 +34187,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -33361,7 +34270,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -33469,7 +34378,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -33539,7 +34448,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -33643,7 +34552,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -33713,7 +34622,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -33797,7 +34706,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -33906,7 +34815,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -34017,7 +34926,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -34126,7 +35035,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -34237,7 +35146,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -34346,7 +35255,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -34457,7 +35366,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -34576,7 +35485,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -34697,7 +35606,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -34818,7 +35727,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -34941,7 +35850,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -35062,7 +35971,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -35185,7 +36094,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -35294,7 +36203,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -35405,7 +36314,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -35509,7 +36418,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -35619,7 +36528,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -35723,7 +36632,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -35833,7 +36742,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -35937,7 +36846,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -36047,7 +36956,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -36183,7 +37092,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -36305,7 +37214,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -36422,7 +37331,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -36531,7 +37440,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -36671,7 +37580,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -36743,7 +37652,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -36822,7 +37731,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -36927,7 +37836,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -37009,7 +37918,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -37140,7 +38049,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -37212,7 +38121,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -37289,7 +38198,7 @@ "x-appwrite": { "method": "listTableLogs", "group": "tables", - "weight": 384, + "weight": 390, "cookies": false, "type": "", "demo": "tablesdb\/list-table-logs.md", @@ -37370,7 +38279,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -37426,6 +38335,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -37454,7 +38371,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -37484,7 +38401,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -37511,7 +38429,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37591,6 +38510,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37622,7 +38547,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -37650,7 +38575,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37708,6 +38634,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -37742,7 +38674,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -37806,6 +38738,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37837,7 +38775,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -37895,6 +38833,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37926,7 +38870,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -37990,6 +38934,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -38018,7 +38970,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -38048,7 +39000,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -38121,6 +39074,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38152,7 +39111,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -38225,6 +39184,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38251,7 +39216,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -38303,6 +39268,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -38331,7 +39311,7 @@ "x-appwrite": { "method": "listRowLogs", "group": "logs", - "weight": 428, + "weight": 434, "cookies": false, "type": "", "demo": "tablesdb\/list-row-logs.md", @@ -38422,7 +39402,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -38500,6 +39480,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38533,7 +39519,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -38611,6 +39597,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38642,7 +39634,7 @@ "x-appwrite": { "method": "getTableUsage", "group": null, - "weight": 385, + "weight": 391, "cookies": false, "type": "", "demo": "tablesdb\/get-table-usage.md", @@ -38731,7 +39723,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 377, + "weight": 383, "cookies": false, "type": "", "demo": "tablesdb\/get-usage.md", @@ -39916,7 +40908,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -39996,7 +40988,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -40080,7 +41072,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -40140,7 +41132,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -40211,7 +41203,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -46040,6 +47032,35 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "migrationList": { "description": "Migrations List", "type": "object", @@ -56554,6 +57575,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 98077f1050..9dd44734bb 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -4893,6 +4893,431 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/{databaseId}": { "get": { "summary": "Get database", @@ -8993,6 +9418,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9053,7 +9486,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9085,7 +9519,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9171,6 +9606,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9232,7 +9673,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9295,6 +9737,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9395,6 +9843,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9486,6 +9940,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9584,6 +10044,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9644,7 +10112,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9724,6 +10193,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9834,6 +10309,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9915,6 +10396,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -10026,6 +10522,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10140,6 +10642,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10375,7 +10883,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -10541,7 +11049,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -10614,7 +11122,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -10866,7 +11374,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -10916,7 +11424,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -10967,7 +11475,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -11027,7 +11535,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -11275,7 +11783,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -11337,7 +11845,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -11415,7 +11923,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -11496,7 +12004,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -11589,7 +12097,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -11675,7 +12183,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -11782,7 +12290,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -11879,7 +12387,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -11942,7 +12450,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -12010,7 +12518,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -12096,7 +12604,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -12164,7 +12672,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -12239,7 +12747,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -12358,7 +12866,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -12424,7 +12932,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -12492,7 +13000,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -12552,7 +13060,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -12643,7 +13151,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -12711,7 +13219,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -12804,7 +13312,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -15215,7 +15723,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": "", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -15413,7 +15921,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -19909,7 +20417,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -19982,7 +20490,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -20250,7 +20758,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -20300,7 +20808,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -20351,7 +20859,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -20411,7 +20919,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -20674,7 +21182,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -20736,7 +21244,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -20814,7 +21322,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -20895,7 +21403,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -20996,7 +21504,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -21076,7 +21584,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -21183,7 +21691,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -21281,7 +21789,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -21344,7 +21852,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -21412,7 +21920,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -21498,7 +22006,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -21566,7 +22074,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -21638,7 +22146,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -21703,7 +22211,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -21771,7 +22279,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -21831,7 +22339,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -21922,7 +22430,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -21990,7 +22498,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -22083,7 +22591,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -23391,7 +23899,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -23464,7 +23972,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -23523,6 +24031,431 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/{databaseId}": { "get": { "summary": "Get database", @@ -23547,7 +24480,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -23607,7 +24540,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -23686,7 +24619,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -23746,7 +24679,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -23830,7 +24763,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -23939,7 +24872,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -24010,7 +24943,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -24115,7 +25048,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -24186,7 +25119,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -24271,7 +25204,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -24381,7 +25314,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -24493,7 +25426,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -24603,7 +25536,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -24715,7 +25648,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -24825,7 +25758,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -24937,7 +25870,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -25057,7 +25990,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -25179,7 +26112,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -25301,7 +26234,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -25425,7 +26358,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -25547,7 +26480,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -25671,7 +26604,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -25781,7 +26714,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -25893,7 +26826,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -25998,7 +26931,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -26109,7 +27042,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -26214,7 +27147,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -26325,7 +27258,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -26430,7 +27363,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -26541,7 +27474,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -26678,7 +27611,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -26801,7 +27734,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -26919,7 +27852,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -27029,7 +27962,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -27170,7 +28103,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -27243,7 +28176,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -27323,7 +28256,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -27429,7 +28362,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -27512,7 +28445,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -27644,7 +28577,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -27717,7 +28650,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -27795,7 +28728,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -27853,6 +28786,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -27881,7 +28822,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -27912,7 +28853,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -27940,7 +28882,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28022,6 +28965,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28053,7 +29002,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -28082,7 +29031,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28141,6 +29091,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -28175,7 +29131,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -28240,6 +29196,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28271,7 +29233,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -28330,6 +29292,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28361,7 +29329,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -28427,6 +29395,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -28455,7 +29431,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -28486,7 +29462,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -28561,6 +29538,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28592,7 +29575,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -28667,6 +29650,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28693,7 +29682,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -28747,6 +29736,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -28777,7 +29781,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -28857,6 +29861,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28890,7 +29900,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -28970,6 +29980,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -30036,7 +31052,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -30117,7 +31133,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -30202,7 +31218,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -30263,7 +31279,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -30335,7 +31351,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -35059,6 +36075,35 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "specificationList": { "description": "Specifications List", "type": "object", @@ -41477,6 +42522,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 55911e9556..4d0e75c6fa 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -4948,6 +4948,419 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/{databaseId}\/collections\/{collectionId}\/documents": { "get": { "summary": "List documents", @@ -5029,6 +5442,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -5088,7 +5509,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5173,6 +5595,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5269,6 +5697,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -5328,7 +5764,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -5406,6 +5843,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -5514,6 +5957,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5593,6 +6042,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -5702,6 +6166,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5814,6 +6284,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -5845,7 +6321,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -5918,7 +6394,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -6035,7 +6511,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -7549,6 +8025,419 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/{databaseId}\/tables\/{tableId}\/rows": { "get": { "summary": "List rows", @@ -7573,7 +8462,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -7629,6 +8518,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -7657,7 +8554,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -7687,7 +8584,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7768,6 +8666,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -7799,7 +8703,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -7863,6 +8767,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -7891,7 +8803,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -7921,7 +8833,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -7994,6 +8907,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8025,7 +8944,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -8098,6 +9017,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8124,7 +9049,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -8176,6 +9101,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -8206,7 +9146,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -8284,6 +9224,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -8317,7 +9263,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -8395,6 +9341,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9931,6 +10883,35 @@ "localeCodes": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "row": { "description": "Row", "type": "object", @@ -11840,6 +12821,59 @@ "recoveryCode": true } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 6d5721c73b..c39987cbaf 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -5052,7 +5052,7 @@ "x-appwrite": { "method": "getResource", "group": null, - "weight": 496, + "weight": 508, "cookies": false, "type": "", "demo": "console\/get-resource.md", @@ -5367,6 +5367,419 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/usage": { "get": { "summary": "Get databases usage stats", @@ -9525,6 +9938,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9584,7 +10005,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9615,7 +10037,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9699,6 +10122,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9759,7 +10188,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9821,6 +10251,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9920,6 +10356,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10010,6 +10452,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10106,6 +10554,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -10165,7 +10621,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -10243,6 +10700,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -10351,6 +10814,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10430,6 +10899,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -10629,6 +11113,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10741,6 +11231,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10974,7 +11470,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -11524,7 +12020,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -11596,7 +12092,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -11847,7 +12343,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -11896,7 +12392,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -11946,7 +12442,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 466, + "weight": 478, "cookies": false, "type": "", "demo": "functions\/list-templates.md", @@ -12040,7 +12536,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 465, + "weight": 477, "cookies": false, "type": "", "demo": "functions\/get-template.md", @@ -12098,7 +12594,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 459, + "weight": 471, "cookies": false, "type": "", "demo": "functions\/list-usage.md", @@ -12168,7 +12664,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -12227,7 +12723,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -12474,7 +12970,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -12535,7 +13031,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -12612,7 +13108,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -12692,7 +13188,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -12784,7 +13280,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -12869,7 +13365,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -12975,7 +13471,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -13071,7 +13567,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -13133,7 +13629,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -13200,7 +13696,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -13285,7 +13781,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -13352,7 +13848,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -13425,7 +13921,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -13542,7 +14038,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -13606,7 +14102,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -13673,7 +14169,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 458, + "weight": 470, "cookies": false, "type": "", "demo": "functions\/get-usage.md", @@ -13751,7 +14247,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -13810,7 +14306,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -13900,7 +14396,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -13967,7 +14463,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -14059,7 +14555,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -16423,7 +16919,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": "", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -16620,7 +17116,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -21355,7 +21851,7 @@ "type": "string", "description": "Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "internalFile": { "type": "boolean", @@ -22590,7 +23086,7 @@ "x-appwrite": { "method": "list", "group": "projects", - "weight": 436, + "weight": 448, "cookies": false, "type": "", "demo": "projects\/list.md", @@ -24233,7 +24729,7 @@ "x-appwrite": { "method": "listDevKeys", "group": "devKeys", - "weight": 434, + "weight": 446, "cookies": false, "type": "", "demo": "projects\/list-dev-keys.md", @@ -24303,7 +24799,7 @@ "x-appwrite": { "method": "createDevKey", "group": "devKeys", - "weight": 431, + "weight": 443, "cookies": false, "type": "", "demo": "projects\/create-dev-key.md", @@ -24386,7 +24882,7 @@ "x-appwrite": { "method": "getDevKey", "group": "devKeys", - "weight": 433, + "weight": 445, "cookies": false, "type": "", "demo": "projects\/get-dev-key.md", @@ -24452,7 +24948,7 @@ "x-appwrite": { "method": "updateDevKey", "group": "devKeys", - "weight": 432, + "weight": 444, "cookies": false, "type": "", "demo": "projects\/update-dev-key.md", @@ -24538,7 +25034,7 @@ "x-appwrite": { "method": "deleteDevKey", "group": "devKeys", - "weight": 435, + "weight": 447, "cookies": false, "type": "", "demo": "projects\/delete-dev-key.md", @@ -28351,7 +28847,7 @@ "x-appwrite": { "method": "listRules", "group": null, - "weight": 502, + "weight": 514, "cookies": false, "type": "", "demo": "proxy\/list-rules.md", @@ -28424,7 +28920,7 @@ "x-appwrite": { "method": "createAPIRule", "group": null, - "weight": 497, + "weight": 509, "cookies": false, "type": "", "demo": "proxy\/create-api-rule.md", @@ -28494,7 +28990,7 @@ "x-appwrite": { "method": "createFunctionRule", "group": null, - "weight": 499, + "weight": 511, "cookies": false, "type": "", "demo": "proxy\/create-function-rule.md", @@ -28577,7 +29073,7 @@ "x-appwrite": { "method": "createRedirectRule", "group": null, - "weight": 500, + "weight": 512, "cookies": false, "type": "", "demo": "proxy\/create-redirect-rule.md", @@ -28697,7 +29193,7 @@ "x-appwrite": { "method": "createSiteRule", "group": null, - "weight": 498, + "weight": 510, "cookies": false, "type": "", "demo": "proxy\/create-site-rule.md", @@ -28778,7 +29274,7 @@ "x-appwrite": { "method": "getRule", "group": null, - "weight": 501, + "weight": 513, "cookies": false, "type": "", "demo": "proxy\/get-rule.md", @@ -28831,7 +29327,7 @@ "x-appwrite": { "method": "deleteRule", "group": null, - "weight": 503, + "weight": 515, "cookies": false, "type": "", "demo": "proxy\/delete-rule.md", @@ -28891,7 +29387,7 @@ "x-appwrite": { "method": "updateRuleVerification", "group": null, - "weight": 504, + "weight": 516, "cookies": false, "type": "", "demo": "proxy\/update-rule-verification.md", @@ -28949,7 +29445,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -29021,7 +29517,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -29288,7 +29784,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -29337,7 +29833,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -29387,7 +29883,7 @@ "x-appwrite": { "method": "listTemplates", "group": "templates", - "weight": 491, + "weight": 503, "cookies": false, "type": "", "demo": "sites\/list-templates.md", @@ -29481,7 +29977,7 @@ "x-appwrite": { "method": "getTemplate", "group": "templates", - "weight": 492, + "weight": 504, "cookies": false, "type": "", "demo": "sites\/get-template.md", @@ -29539,7 +30035,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 493, + "weight": 505, "cookies": false, "type": "", "demo": "sites\/list-usage.md", @@ -29609,7 +30105,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -29668,7 +30164,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -29930,7 +30426,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -29991,7 +30487,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -30068,7 +30564,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -30148,7 +30644,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -30248,7 +30744,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -30327,7 +30823,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -30433,7 +30929,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -30530,7 +31026,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -30592,7 +31088,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -30659,7 +31155,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -30744,7 +31240,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -30811,7 +31307,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -30882,7 +31378,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -30946,7 +31442,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -31013,7 +31509,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 494, + "weight": 506, "cookies": false, "type": "", "demo": "sites\/get-usage.md", @@ -31091,7 +31587,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -31150,7 +31646,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -31240,7 +31736,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -31307,7 +31803,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -31399,7 +31895,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -32833,7 +33329,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -32905,7 +33401,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -32963,6 +33459,419 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/usage": { "get": { "summary": "Get TablesDB usage stats", @@ -32987,7 +33896,7 @@ "x-appwrite": { "method": "listUsage", "group": null, - "weight": 378, + "weight": 384, "cookies": false, "type": "", "demo": "tablesdb\/list-usage.md", @@ -33082,7 +33991,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -33141,7 +34050,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -33219,7 +34128,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -33278,7 +34187,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -33361,7 +34270,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -33469,7 +34378,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -33539,7 +34448,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -33643,7 +34552,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -33713,7 +34622,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -33797,7 +34706,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -33906,7 +34815,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -34017,7 +34926,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -34126,7 +35035,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -34237,7 +35146,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -34346,7 +35255,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -34457,7 +35366,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -34576,7 +35485,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -34697,7 +35606,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -34818,7 +35727,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -34941,7 +35850,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -35062,7 +35971,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -35185,7 +36094,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -35294,7 +36203,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -35405,7 +36314,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -35509,7 +36418,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -35619,7 +36528,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -35723,7 +36632,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -35833,7 +36742,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -35937,7 +36846,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -36047,7 +36956,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -36183,7 +37092,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -36305,7 +37214,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -36422,7 +37331,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -36531,7 +37440,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -36671,7 +37580,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -36743,7 +37652,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -36822,7 +37731,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -36927,7 +37836,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -37009,7 +37918,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -37140,7 +38049,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -37212,7 +38121,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -37289,7 +38198,7 @@ "x-appwrite": { "method": "listTableLogs", "group": "tables", - "weight": 384, + "weight": 390, "cookies": false, "type": "", "demo": "tablesdb\/list-table-logs.md", @@ -37370,7 +38279,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -37426,6 +38335,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -37454,7 +38371,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -37484,7 +38401,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -37511,7 +38429,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37591,6 +38510,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37622,7 +38547,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -37650,7 +38575,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -37708,6 +38634,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -37742,7 +38674,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -37806,6 +38738,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37837,7 +38775,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -37895,6 +38833,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -37926,7 +38870,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -37990,6 +38934,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -38018,7 +38970,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -38048,7 +39000,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -38121,6 +39074,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38152,7 +39111,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -38225,6 +39184,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38251,7 +39216,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -38303,6 +39268,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -38331,7 +39311,7 @@ "x-appwrite": { "method": "listRowLogs", "group": "logs", - "weight": 428, + "weight": 434, "cookies": false, "type": "", "demo": "tablesdb\/list-row-logs.md", @@ -38422,7 +39402,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -38500,6 +39480,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38533,7 +39519,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -38611,6 +39597,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -38642,7 +39634,7 @@ "x-appwrite": { "method": "getTableUsage", "group": null, - "weight": 385, + "weight": 391, "cookies": false, "type": "", "demo": "tablesdb\/get-table-usage.md", @@ -38731,7 +39723,7 @@ "x-appwrite": { "method": "getUsage", "group": null, - "weight": 377, + "weight": 383, "cookies": false, "type": "", "demo": "tablesdb\/get-usage.md", @@ -39916,7 +40908,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -39996,7 +40988,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -40080,7 +41072,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -40140,7 +41132,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -40211,7 +41203,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -46040,6 +47032,35 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "migrationList": { "description": "Migrations List", "type": "object", @@ -56554,6 +57575,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 98077f1050..7e0785fc0a 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -4893,6 +4893,431 @@ ] } }, + "\/databases\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "databasesListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 376, + "cookies": false, + "type": "", + "demo": "databases\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "databasesCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 372, + "cookies": false, + "type": "", + "demo": "databases\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/databases\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "databasesGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 373, + "cookies": false, + "type": "", + "demo": "databases\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "databasesUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 374, + "cookies": false, + "type": "", + "demo": "databases\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "databasesDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "databases" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 375, + "cookies": false, + "type": "", + "demo": "databases\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/databases\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "databasesCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "databases" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 377, + "cookies": false, + "type": "", + "demo": "databases\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/databases\/{databaseId}": { "get": { "summary": "Get database", @@ -8993,6 +9418,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9053,7 +9486,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9085,7 +9519,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9171,6 +9606,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9232,7 +9673,8 @@ "parameters": [ "databaseId", "collectionId", - "documents" + "documents", + "transactionId" ], "required": [ "databaseId", @@ -9295,6 +9737,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9395,6 +9843,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9486,6 +9940,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9584,6 +10044,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -9644,7 +10112,8 @@ "collectionId", "documentId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -9724,6 +10193,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -9834,6 +10309,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -9915,6 +10396,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -10026,6 +10522,12 @@ "description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10140,6 +10642,12 @@ "description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -10375,7 +10883,7 @@ "tags": [ "databases" ], - "description": "Get index by ID.", + "description": "Get an index by its unique ID.", "responses": { "200": { "description": "Index", @@ -10541,7 +11049,7 @@ "x-appwrite": { "method": "list", "group": "functions", - "weight": 440, + "weight": 452, "cookies": false, "type": "", "demo": "functions\/list.md", @@ -10614,7 +11122,7 @@ "x-appwrite": { "method": "create", "group": "functions", - "weight": 437, + "weight": 449, "cookies": false, "type": "", "demo": "functions\/create.md", @@ -10866,7 +11374,7 @@ "x-appwrite": { "method": "listRuntimes", "group": "runtimes", - "weight": 442, + "weight": 454, "cookies": false, "type": "", "demo": "functions\/list-runtimes.md", @@ -10916,7 +11424,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "runtimes", - "weight": 443, + "weight": 455, "cookies": false, "type": "", "demo": "functions\/list-specifications.md", @@ -10967,7 +11475,7 @@ "x-appwrite": { "method": "get", "group": "functions", - "weight": 438, + "weight": 450, "cookies": false, "type": "", "demo": "functions\/get.md", @@ -11027,7 +11535,7 @@ "x-appwrite": { "method": "update", "group": "functions", - "weight": 439, + "weight": 451, "cookies": false, "type": "", "demo": "functions\/update.md", @@ -11275,7 +11783,7 @@ "x-appwrite": { "method": "delete", "group": "functions", - "weight": 441, + "weight": 453, "cookies": false, "type": "", "demo": "functions\/delete.md", @@ -11337,7 +11845,7 @@ "x-appwrite": { "method": "updateFunctionDeployment", "group": "functions", - "weight": 446, + "weight": 458, "cookies": false, "type": "", "demo": "functions\/update-function-deployment.md", @@ -11415,7 +11923,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 447, + "weight": 459, "cookies": false, "type": "", "demo": "functions\/list-deployments.md", @@ -11496,7 +12004,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 444, + "weight": 456, "cookies": false, "type": "upload", "demo": "functions\/create-deployment.md", @@ -11589,7 +12097,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 452, + "weight": 464, "cookies": false, "type": "", "demo": "functions\/create-duplicate-deployment.md", @@ -11675,7 +12183,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 449, + "weight": 461, "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", @@ -11782,7 +12290,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 450, + "weight": 462, "cookies": false, "type": "", "demo": "functions\/create-vcs-deployment.md", @@ -11879,7 +12387,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 445, + "weight": 457, "cookies": false, "type": "", "demo": "functions\/get-deployment.md", @@ -11942,7 +12450,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 448, + "weight": 460, "cookies": false, "type": "", "demo": "functions\/delete-deployment.md", @@ -12010,7 +12518,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 451, + "weight": 463, "cookies": false, "type": "location", "demo": "functions\/get-deployment-download.md", @@ -12096,7 +12604,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 453, + "weight": 465, "cookies": false, "type": "", "demo": "functions\/update-deployment-status.md", @@ -12164,7 +12672,7 @@ "x-appwrite": { "method": "listExecutions", "group": "executions", - "weight": 456, + "weight": 468, "cookies": false, "type": "", "demo": "functions\/list-executions.md", @@ -12239,7 +12747,7 @@ "x-appwrite": { "method": "createExecution", "group": "executions", - "weight": 454, + "weight": 466, "cookies": false, "type": "", "demo": "functions\/create-execution.md", @@ -12358,7 +12866,7 @@ "x-appwrite": { "method": "getExecution", "group": "executions", - "weight": 455, + "weight": 467, "cookies": false, "type": "", "demo": "functions\/get-execution.md", @@ -12424,7 +12932,7 @@ "x-appwrite": { "method": "deleteExecution", "group": "executions", - "weight": 457, + "weight": 469, "cookies": false, "type": "", "demo": "functions\/delete-execution.md", @@ -12492,7 +13000,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 462, + "weight": 474, "cookies": false, "type": "", "demo": "functions\/list-variables.md", @@ -12552,7 +13060,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 460, + "weight": 472, "cookies": false, "type": "", "demo": "functions\/create-variable.md", @@ -12643,7 +13151,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 461, + "weight": 473, "cookies": false, "type": "", "demo": "functions\/get-variable.md", @@ -12711,7 +13219,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 463, + "weight": 475, "cookies": false, "type": "", "demo": "functions\/update-variable.md", @@ -12804,7 +13312,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 464, + "weight": 476, "cookies": false, "type": "", "demo": "functions\/delete-variable.md", @@ -15215,7 +15723,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": "", - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -15413,7 +15921,7 @@ "type": "string", "description": "Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as :.", "default": null, - "x-example": "[ID1:ID2]" + "x-example": "" }, "icon": { "type": "string", @@ -19909,7 +20417,7 @@ "x-appwrite": { "method": "list", "group": "sites", - "weight": 469, + "weight": 481, "cookies": false, "type": "", "demo": "sites\/list.md", @@ -19982,7 +20490,7 @@ "x-appwrite": { "method": "create", "group": "sites", - "weight": 467, + "weight": 479, "cookies": false, "type": "", "demo": "sites\/create.md", @@ -20250,7 +20758,7 @@ "x-appwrite": { "method": "listFrameworks", "group": "frameworks", - "weight": 472, + "weight": 484, "cookies": false, "type": "", "demo": "sites\/list-frameworks.md", @@ -20300,7 +20808,7 @@ "x-appwrite": { "method": "listSpecifications", "group": "frameworks", - "weight": 495, + "weight": 507, "cookies": false, "type": "", "demo": "sites\/list-specifications.md", @@ -20351,7 +20859,7 @@ "x-appwrite": { "method": "get", "group": "sites", - "weight": 468, + "weight": 480, "cookies": false, "type": "", "demo": "sites\/get.md", @@ -20411,7 +20919,7 @@ "x-appwrite": { "method": "update", "group": "sites", - "weight": 470, + "weight": 482, "cookies": false, "type": "", "demo": "sites\/update.md", @@ -20674,7 +21182,7 @@ "x-appwrite": { "method": "delete", "group": "sites", - "weight": 471, + "weight": 483, "cookies": false, "type": "", "demo": "sites\/delete.md", @@ -20736,7 +21244,7 @@ "x-appwrite": { "method": "updateSiteDeployment", "group": "sites", - "weight": 478, + "weight": 490, "cookies": false, "type": "", "demo": "sites\/update-site-deployment.md", @@ -20814,7 +21322,7 @@ "x-appwrite": { "method": "listDeployments", "group": "deployments", - "weight": 477, + "weight": 489, "cookies": false, "type": "", "demo": "sites\/list-deployments.md", @@ -20895,7 +21403,7 @@ "x-appwrite": { "method": "createDeployment", "group": "deployments", - "weight": 473, + "weight": 485, "cookies": false, "type": "upload", "demo": "sites\/create-deployment.md", @@ -20996,7 +21504,7 @@ "x-appwrite": { "method": "createDuplicateDeployment", "group": "deployments", - "weight": 481, + "weight": 493, "cookies": false, "type": "", "demo": "sites\/create-duplicate-deployment.md", @@ -21076,7 +21584,7 @@ "x-appwrite": { "method": "createTemplateDeployment", "group": "deployments", - "weight": 474, + "weight": 486, "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", @@ -21183,7 +21691,7 @@ "x-appwrite": { "method": "createVcsDeployment", "group": "deployments", - "weight": 475, + "weight": 487, "cookies": false, "type": "", "demo": "sites\/create-vcs-deployment.md", @@ -21281,7 +21789,7 @@ "x-appwrite": { "method": "getDeployment", "group": "deployments", - "weight": 476, + "weight": 488, "cookies": false, "type": "", "demo": "sites\/get-deployment.md", @@ -21344,7 +21852,7 @@ "x-appwrite": { "method": "deleteDeployment", "group": "deployments", - "weight": 479, + "weight": 491, "cookies": false, "type": "", "demo": "sites\/delete-deployment.md", @@ -21412,7 +21920,7 @@ "x-appwrite": { "method": "getDeploymentDownload", "group": "deployments", - "weight": 480, + "weight": 492, "cookies": false, "type": "location", "demo": "sites\/get-deployment-download.md", @@ -21498,7 +22006,7 @@ "x-appwrite": { "method": "updateDeploymentStatus", "group": "deployments", - "weight": 482, + "weight": 494, "cookies": false, "type": "", "demo": "sites\/update-deployment-status.md", @@ -21566,7 +22074,7 @@ "x-appwrite": { "method": "listLogs", "group": "logs", - "weight": 484, + "weight": 496, "cookies": false, "type": "", "demo": "sites\/list-logs.md", @@ -21638,7 +22146,7 @@ "x-appwrite": { "method": "getLog", "group": "logs", - "weight": 483, + "weight": 495, "cookies": false, "type": "", "demo": "sites\/get-log.md", @@ -21703,7 +22211,7 @@ "x-appwrite": { "method": "deleteLog", "group": "logs", - "weight": 485, + "weight": 497, "cookies": false, "type": "", "demo": "sites\/delete-log.md", @@ -21771,7 +22279,7 @@ "x-appwrite": { "method": "listVariables", "group": "variables", - "weight": 488, + "weight": 500, "cookies": false, "type": "", "demo": "sites\/list-variables.md", @@ -21831,7 +22339,7 @@ "x-appwrite": { "method": "createVariable", "group": "variables", - "weight": 486, + "weight": 498, "cookies": false, "type": "", "demo": "sites\/create-variable.md", @@ -21922,7 +22430,7 @@ "x-appwrite": { "method": "getVariable", "group": "variables", - "weight": 487, + "weight": 499, "cookies": false, "type": "", "demo": "sites\/get-variable.md", @@ -21990,7 +22498,7 @@ "x-appwrite": { "method": "updateVariable", "group": "variables", - "weight": 489, + "weight": 501, "cookies": false, "type": "", "demo": "sites\/update-variable.md", @@ -22083,7 +22591,7 @@ "x-appwrite": { "method": "deleteVariable", "group": "variables", - "weight": 490, + "weight": 502, "cookies": false, "type": "", "demo": "sites\/delete-variable.md", @@ -23391,7 +23899,7 @@ "x-appwrite": { "method": "list", "group": "tablesdb", - "weight": 376, + "weight": 382, "cookies": false, "type": "", "demo": "tablesdb\/list.md", @@ -23464,7 +23972,7 @@ "x-appwrite": { "method": "create", "group": "tablesdb", - "weight": 372, + "weight": 378, "cookies": false, "type": "", "demo": "tablesdb\/create.md", @@ -23523,6 +24031,431 @@ ] } }, + "\/tablesdb\/transactions": { + "get": { + "summary": "List transactions", + "operationId": "tablesDBListTransactions", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "List transactions across all databases.", + "responses": { + "200": { + "description": "Transaction List", + "schema": { + "$ref": "#\/definitions\/transactionList" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "listTransactions", + "group": "transactions", + "weight": 441, + "cookies": false, + "type": "", + "demo": "tablesdb\/list-transactions.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/list-transactions.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries).", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + } + ] + }, + "post": { + "summary": "Create transaction", + "operationId": "tablesDBCreateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create a new transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createTransaction", + "group": "transactions", + "weight": 437, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "ttl": { + "type": "integer", + "description": "Seconds before the transaction expires.", + "default": 300, + "x-example": 60 + } + } + } + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}": { + "get": { + "summary": "Get transaction", + "operationId": "tablesDBGetTransaction", + "consumes": [], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Get a transaction by its unique ID.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "getTransaction", + "group": "transactions", + "weight": 438, + "cookies": false, + "type": "", + "demo": "tablesdb\/get-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/get-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.read", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "patch": { + "summary": "Update transaction", + "operationId": "tablesDBUpdateTransaction", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Update a transaction, to either commit or roll back its operations.", + "responses": { + "200": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "updateTransaction", + "group": "transactions", + "weight": 439, + "cookies": false, + "type": "", + "demo": "tablesdb\/update-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/update-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "boolean", + "description": "Commit transaction?", + "default": false, + "x-example": false + }, + "rollback": { + "type": "boolean", + "description": "Rollback transaction?", + "default": false, + "x-example": false + } + } + } + } + ] + }, + "delete": { + "summary": "Delete transaction", + "operationId": "tablesDBDeleteTransaction", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "tablesDB" + ], + "description": "Delete a transaction by its unique ID.", + "responses": { + "204": { + "description": "No content" + } + }, + "deprecated": false, + "x-appwrite": { + "method": "deleteTransaction", + "group": "transactions", + "weight": 440, + "cookies": false, + "type": "", + "demo": "tablesdb\/delete-transaction.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/delete-transaction.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, + "\/tablesdb\/transactions\/{transactionId}\/operations": { + "post": { + "summary": "Add operations to transaction", + "operationId": "tablesDBCreateOperations", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "tablesDB" + ], + "description": "Create multiple operations in a single transaction.", + "responses": { + "201": { + "description": "Transaction", + "schema": { + "$ref": "#\/definitions\/transaction" + } + } + }, + "deprecated": false, + "x-appwrite": { + "method": "createOperations", + "group": "transactions", + "weight": 442, + "cookies": false, + "type": "", + "demo": "tablesdb\/create-operations.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/tablesdb\/create-operations.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "transactions.write", + "platforms": [ + "server", + "client" + ], + "packaging": false, + "auth": { + "Project": [], + "Key": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "Session": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "transactionId", + "description": "Transaction ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Array of staged operations.", + "default": [], + "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "items": { + "type": "object" + } + } + } + } + } + ] + } + }, "\/tablesdb\/{databaseId}": { "get": { "summary": "Get database", @@ -23547,7 +24480,7 @@ "x-appwrite": { "method": "get", "group": "tablesdb", - "weight": 373, + "weight": 379, "cookies": false, "type": "", "demo": "tablesdb\/get.md", @@ -23607,7 +24540,7 @@ "x-appwrite": { "method": "update", "group": "tablesdb", - "weight": 374, + "weight": 380, "cookies": false, "type": "", "demo": "tablesdb\/update.md", @@ -23686,7 +24619,7 @@ "x-appwrite": { "method": "delete", "group": "tablesdb", - "weight": 375, + "weight": 381, "cookies": false, "type": "", "demo": "tablesdb\/delete.md", @@ -23746,7 +24679,7 @@ "x-appwrite": { "method": "listTables", "group": "tables", - "weight": 383, + "weight": 389, "cookies": false, "type": "", "demo": "tablesdb\/list-tables.md", @@ -23830,7 +24763,7 @@ "x-appwrite": { "method": "createTable", "group": "tables", - "weight": 379, + "weight": 385, "cookies": false, "type": "", "demo": "tablesdb\/create-table.md", @@ -23939,7 +24872,7 @@ "x-appwrite": { "method": "getTable", "group": "tables", - "weight": 380, + "weight": 386, "cookies": false, "type": "", "demo": "tablesdb\/get-table.md", @@ -24010,7 +24943,7 @@ "x-appwrite": { "method": "updateTable", "group": "tables", - "weight": 381, + "weight": 387, "cookies": false, "type": "", "demo": "tablesdb\/update-table.md", @@ -24115,7 +25048,7 @@ "x-appwrite": { "method": "deleteTable", "group": "tables", - "weight": 382, + "weight": 388, "cookies": false, "type": "", "demo": "tablesdb\/delete-table.md", @@ -24186,7 +25119,7 @@ "x-appwrite": { "method": "listColumns", "group": "columns", - "weight": 388, + "weight": 394, "cookies": false, "type": "", "demo": "tablesdb\/list-columns.md", @@ -24271,7 +25204,7 @@ "x-appwrite": { "method": "createBooleanColumn", "group": "columns", - "weight": 389, + "weight": 395, "cookies": false, "type": "", "demo": "tablesdb\/create-boolean-column.md", @@ -24381,7 +25314,7 @@ "x-appwrite": { "method": "updateBooleanColumn", "group": "columns", - "weight": 390, + "weight": 396, "cookies": false, "type": "", "demo": "tablesdb\/update-boolean-column.md", @@ -24493,7 +25426,7 @@ "x-appwrite": { "method": "createDatetimeColumn", "group": "columns", - "weight": 391, + "weight": 397, "cookies": false, "type": "", "demo": "tablesdb\/create-datetime-column.md", @@ -24603,7 +25536,7 @@ "x-appwrite": { "method": "updateDatetimeColumn", "group": "columns", - "weight": 392, + "weight": 398, "cookies": false, "type": "", "demo": "tablesdb\/update-datetime-column.md", @@ -24715,7 +25648,7 @@ "x-appwrite": { "method": "createEmailColumn", "group": "columns", - "weight": 393, + "weight": 399, "cookies": false, "type": "", "demo": "tablesdb\/create-email-column.md", @@ -24825,7 +25758,7 @@ "x-appwrite": { "method": "updateEmailColumn", "group": "columns", - "weight": 394, + "weight": 400, "cookies": false, "type": "", "demo": "tablesdb\/update-email-column.md", @@ -24937,7 +25870,7 @@ "x-appwrite": { "method": "createEnumColumn", "group": "columns", - "weight": 395, + "weight": 401, "cookies": false, "type": "", "demo": "tablesdb\/create-enum-column.md", @@ -25057,7 +25990,7 @@ "x-appwrite": { "method": "updateEnumColumn", "group": "columns", - "weight": 396, + "weight": 402, "cookies": false, "type": "", "demo": "tablesdb\/update-enum-column.md", @@ -25179,7 +26112,7 @@ "x-appwrite": { "method": "createFloatColumn", "group": "columns", - "weight": 397, + "weight": 403, "cookies": false, "type": "", "demo": "tablesdb\/create-float-column.md", @@ -25301,7 +26234,7 @@ "x-appwrite": { "method": "updateFloatColumn", "group": "columns", - "weight": 398, + "weight": 404, "cookies": false, "type": "", "demo": "tablesdb\/update-float-column.md", @@ -25425,7 +26358,7 @@ "x-appwrite": { "method": "createIntegerColumn", "group": "columns", - "weight": 399, + "weight": 405, "cookies": false, "type": "", "demo": "tablesdb\/create-integer-column.md", @@ -25547,7 +26480,7 @@ "x-appwrite": { "method": "updateIntegerColumn", "group": "columns", - "weight": 400, + "weight": 406, "cookies": false, "type": "", "demo": "tablesdb\/update-integer-column.md", @@ -25671,7 +26604,7 @@ "x-appwrite": { "method": "createIpColumn", "group": "columns", - "weight": 401, + "weight": 407, "cookies": false, "type": "", "demo": "tablesdb\/create-ip-column.md", @@ -25781,7 +26714,7 @@ "x-appwrite": { "method": "updateIpColumn", "group": "columns", - "weight": 402, + "weight": 408, "cookies": false, "type": "", "demo": "tablesdb\/update-ip-column.md", @@ -25893,7 +26826,7 @@ "x-appwrite": { "method": "createLineColumn", "group": "columns", - "weight": 403, + "weight": 409, "cookies": false, "type": "", "demo": "tablesdb\/create-line-column.md", @@ -25998,7 +26931,7 @@ "x-appwrite": { "method": "updateLineColumn", "group": "columns", - "weight": 404, + "weight": 410, "cookies": false, "type": "", "demo": "tablesdb\/update-line-column.md", @@ -26109,7 +27042,7 @@ "x-appwrite": { "method": "createPointColumn", "group": "columns", - "weight": 405, + "weight": 411, "cookies": false, "type": "", "demo": "tablesdb\/create-point-column.md", @@ -26214,7 +27147,7 @@ "x-appwrite": { "method": "updatePointColumn", "group": "columns", - "weight": 406, + "weight": 412, "cookies": false, "type": "", "demo": "tablesdb\/update-point-column.md", @@ -26325,7 +27258,7 @@ "x-appwrite": { "method": "createPolygonColumn", "group": "columns", - "weight": 407, + "weight": 413, "cookies": false, "type": "", "demo": "tablesdb\/create-polygon-column.md", @@ -26430,7 +27363,7 @@ "x-appwrite": { "method": "updatePolygonColumn", "group": "columns", - "weight": 408, + "weight": 414, "cookies": false, "type": "", "demo": "tablesdb\/update-polygon-column.md", @@ -26541,7 +27474,7 @@ "x-appwrite": { "method": "createRelationshipColumn", "group": "columns", - "weight": 409, + "weight": 415, "cookies": false, "type": "", "demo": "tablesdb\/create-relationship-column.md", @@ -26678,7 +27611,7 @@ "x-appwrite": { "method": "createStringColumn", "group": "columns", - "weight": 411, + "weight": 417, "cookies": false, "type": "", "demo": "tablesdb\/create-string-column.md", @@ -26801,7 +27734,7 @@ "x-appwrite": { "method": "updateStringColumn", "group": "columns", - "weight": 412, + "weight": 418, "cookies": false, "type": "", "demo": "tablesdb\/update-string-column.md", @@ -26919,7 +27852,7 @@ "x-appwrite": { "method": "createUrlColumn", "group": "columns", - "weight": 413, + "weight": 419, "cookies": false, "type": "", "demo": "tablesdb\/create-url-column.md", @@ -27029,7 +27962,7 @@ "x-appwrite": { "method": "updateUrlColumn", "group": "columns", - "weight": 414, + "weight": 420, "cookies": false, "type": "", "demo": "tablesdb\/update-url-column.md", @@ -27170,7 +28103,7 @@ "x-appwrite": { "method": "getColumn", "group": "columns", - "weight": 386, + "weight": 392, "cookies": false, "type": "", "demo": "tablesdb\/get-column.md", @@ -27243,7 +28176,7 @@ "x-appwrite": { "method": "deleteColumn", "group": "columns", - "weight": 387, + "weight": 393, "cookies": false, "type": "", "demo": "tablesdb\/delete-column.md", @@ -27323,7 +28256,7 @@ "x-appwrite": { "method": "updateRelationshipColumn", "group": "columns", - "weight": 410, + "weight": 416, "cookies": false, "type": "", "demo": "tablesdb\/update-relationship-column.md", @@ -27429,7 +28362,7 @@ "x-appwrite": { "method": "listIndexes", "group": "indexes", - "weight": 418, + "weight": 424, "cookies": false, "type": "", "demo": "tablesdb\/list-indexes.md", @@ -27512,7 +28445,7 @@ "x-appwrite": { "method": "createIndex", "group": "indexes", - "weight": 415, + "weight": 421, "cookies": false, "type": "", "demo": "tablesdb\/create-index.md", @@ -27644,7 +28577,7 @@ "x-appwrite": { "method": "getIndex", "group": "indexes", - "weight": 416, + "weight": 422, "cookies": false, "type": "", "demo": "tablesdb\/get-index.md", @@ -27717,7 +28650,7 @@ "x-appwrite": { "method": "deleteIndex", "group": "indexes", - "weight": 417, + "weight": 423, "cookies": false, "type": "", "demo": "tablesdb\/delete-index.md", @@ -27795,7 +28728,7 @@ "x-appwrite": { "method": "listRows", "group": "rows", - "weight": 427, + "weight": 433, "cookies": false, "type": "", "demo": "tablesdb\/list-rows.md", @@ -27853,6 +28786,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -27881,7 +28822,7 @@ "x-appwrite": { "method": "createRow", "group": "rows", - "weight": 419, + "weight": 425, "cookies": false, "type": "", "demo": "tablesdb\/create-row.md", @@ -27912,7 +28853,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -27940,7 +28882,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28022,6 +28965,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28053,7 +29002,7 @@ "x-appwrite": { "method": "upsertRows", "group": "rows", - "weight": 424, + "weight": 430, "cookies": false, "type": "", "demo": "tablesdb\/upsert-rows.md", @@ -28082,7 +29031,8 @@ "parameters": [ "databaseId", "tableId", - "rows" + "rows", + "transactionId" ], "required": [ "databaseId", @@ -28141,6 +29091,12 @@ "items": { "type": "object" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } }, "required": [ @@ -28175,7 +29131,7 @@ "x-appwrite": { "method": "updateRows", "group": "rows", - "weight": 422, + "weight": 428, "cookies": false, "type": "", "demo": "tablesdb\/update-rows.md", @@ -28240,6 +29196,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28271,7 +29233,7 @@ "x-appwrite": { "method": "deleteRows", "group": "rows", - "weight": 426, + "weight": 432, "cookies": false, "type": "", "demo": "tablesdb\/delete-rows.md", @@ -28330,6 +29292,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28361,7 +29329,7 @@ "x-appwrite": { "method": "getRow", "group": "rows", - "weight": 420, + "weight": 426, "cookies": false, "type": "", "demo": "tablesdb\/get-row.md", @@ -28427,6 +29395,14 @@ }, "default": [], "in": "query" + }, + { + "name": "transactionId", + "description": "Transaction ID to read uncommitted changes within the transaction.", + "required": false, + "type": "string", + "x-example": "", + "in": "query" } ] }, @@ -28455,7 +29431,7 @@ "x-appwrite": { "method": "upsertRow", "group": "rows", - "weight": 423, + "weight": 429, "cookies": false, "type": "", "demo": "tablesdb\/upsert-row.md", @@ -28486,7 +29462,8 @@ "tableId", "rowId", "data", - "permissions" + "permissions", + "transactionId" ], "required": [ "databaseId", @@ -28561,6 +29538,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28592,7 +29575,7 @@ "x-appwrite": { "method": "updateRow", "group": "rows", - "weight": 421, + "weight": 427, "cookies": false, "type": "", "demo": "tablesdb\/update-row.md", @@ -28667,6 +29650,12 @@ "items": { "type": "string" } + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28693,7 +29682,7 @@ "x-appwrite": { "method": "deleteRow", "group": "rows", - "weight": 425, + "weight": 431, "cookies": false, "type": "", "demo": "tablesdb\/delete-row.md", @@ -28747,6 +29736,21 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" + } + } + } } ] } @@ -28777,7 +29781,7 @@ "x-appwrite": { "method": "decrementRowColumn", "group": "rows", - "weight": 430, + "weight": 436, "cookies": false, "type": "", "demo": "tablesdb\/decrement-row-column.md", @@ -28857,6 +29861,12 @@ "description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -28890,7 +29900,7 @@ "x-appwrite": { "method": "incrementRowColumn", "group": "rows", - "weight": 429, + "weight": 435, "cookies": false, "type": "", "demo": "tablesdb\/increment-row-column.md", @@ -28970,6 +29980,12 @@ "description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.", "default": null, "x-example": null + }, + "transactionId": { + "type": "string", + "description": "Transaction ID for staging the operation.", + "default": null, + "x-example": "" } } } @@ -30036,7 +31052,7 @@ "x-appwrite": { "method": "list", "group": "files", - "weight": 507, + "weight": 519, "cookies": false, "type": "", "demo": "tokens\/list.md", @@ -30117,7 +31133,7 @@ "x-appwrite": { "method": "createFileToken", "group": "files", - "weight": 505, + "weight": 517, "cookies": false, "type": "", "demo": "tokens\/create-file-token.md", @@ -30202,7 +31218,7 @@ "x-appwrite": { "method": "get", "group": "tokens", - "weight": 506, + "weight": 518, "cookies": false, "type": "", "demo": "tokens\/get.md", @@ -30263,7 +31279,7 @@ "x-appwrite": { "method": "update", "group": "tokens", - "weight": 508, + "weight": 520, "cookies": false, "type": "", "demo": "tokens\/update.md", @@ -30335,7 +31351,7 @@ "x-appwrite": { "method": "delete", "group": "tokens", - "weight": 509, + "weight": 521, "cookies": false, "type": "", "demo": "tokens\/delete.md", @@ -35059,6 +36075,35 @@ "targets": "" } }, + "transactionList": { + "description": "Transaction List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of transactions that matched your query.", + "x-example": 5, + "format": "int32" + }, + "transactions": { + "type": "array", + "description": "List of transactions.", + "items": { + "type": "object", + "$ref": "#\/definitions\/transaction" + }, + "x-example": "" + } + }, + "required": [ + "total", + "transactions" + ], + "example": { + "total": 5, + "transactions": "" + } + }, "specificationList": { "description": "Specifications List", "type": "object", @@ -41477,6 +42522,59 @@ "subscribe": "users" } }, + "transaction": { + "description": "Transaction", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Transaction ID.", + "x-example": "259125845563242502" + }, + "$createdAt": { + "type": "string", + "description": "Transaction creation time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Transaction update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "status": { + "type": "string", + "description": "Current status of the transaction. One of: pending, committing, committed, rolled_back, failed.", + "x-example": "pending" + }, + "operations": { + "type": "integer", + "description": "Number of operations in the transaction.", + "x-example": 5, + "format": "int32" + }, + "expiresAt": { + "type": "string", + "description": "Expiration time in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "status", + "operations", + "expiresAt" + ], + "example": { + "$id": "259125845563242502", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "status": "pending", + "operations": 5, + "expiresAt": "2020-10-15T06:38:00.000+00:00" + } + }, "subscriber": { "description": "Subscriber", "type": "object", diff --git a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php index c9c2135ab3..3dd1faf175 100644 --- a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php @@ -8,6 +8,7 @@ use Appwrite\SDK\MethodType; use Appwrite\SDK\Response; use Appwrite\SDK\Specification\Format; use Appwrite\Template\Template; +use Appwrite\Utopia\Database\Validator\Operation; use Appwrite\Utopia\Response\Model; use Utopia\Database\Database; use Utopia\Database\Helpers\Permission; @@ -396,7 +397,37 @@ class OpenAPI3 extends Format $validator = $validator->getValidator(); } - switch ((!empty($validator)) ? \get_class($validator) : '') { + $class = !empty($validator) + ? \get_class($validator) + : ''; + + $base = !empty($class) + ? \get_parent_class($class) + : ''; + + switch ($base) { + case 'Appwrite\Utopia\Database\Validator\Queries\Base': + $class = $base; + break; + } + + if ($class === 'Utopia\Validator\AnyOf') { + $validator = $param['validator']->getValidators()[0]; + $class = \get_class($validator); + } + + $array = false; + if ($class === 'Utopia\Validator\ArrayList') { + $array = true; + $subclass = \get_class($validator->getValidator()); + switch ($subclass) { + case 'Appwrite\Utopia\Database\Validator\Operation': + $class = $subclass; + break; + } + } + + switch ($class) { case 'Utopia\Database\Validator\UID': case 'Utopia\Validator\Text': $node['schema']['type'] = $validator->getType(); @@ -418,6 +449,20 @@ class OpenAPI3 extends Format $node['schema']['format'] = 'datetime'; $node['schema']['x-example'] = Model::TYPE_DATETIME_EXAMPLE; break; + case 'Utopia\Database\Validator\Spatial': + /** @var Spatial $validator */ + $node['schema']['type'] = 'array'; + $node['schema']['items'] = [ + 'oneOf' => [ + ['type' => 'array'] + ] + ]; + $node['schema']['x-example'] = match ($validator->getSpatialType()) { + Database::VAR_POINT => '[1, 2]', + Database::VAR_LINESTRING => '[[1, 2], [3, 4], [5, 6]]', + Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]', + }; + break; case 'Appwrite\Network\Validator\Email': $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'email'; @@ -449,20 +494,7 @@ class OpenAPI3 extends Format 'type' => $validator->getValidator()->getType(), ]; break; - case 'Utopia\Database\Validator\Spatial': - /** @var Spatial $validator */ - $node['schema']['type'] = 'array'; - $node['schema']['items'] = [ - 'oneOf' => [ - ['type' => 'array'] - ] - ]; - $node['schema']['x-example'] = match ($validator->getSpatialType()) { - Database::VAR_POINT => '[1, 2]', - Database::VAR_LINESTRING => '[[1, 2], [3, 4], [5, 6]]', - Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]', - }; - break; + case 'Appwrite\Utopia\Database\Validator\Queries\Base': case 'Appwrite\Utopia\Database\Validator\Queries\Columns': case 'Appwrite\Utopia\Database\Validator\Queries\Attributes': case 'Appwrite\Utopia\Database\Validator\Queries\Buckets': @@ -556,8 +588,7 @@ class OpenAPI3 extends Format break; } } - - if ($allowed) { + if ($allowed && $validator->getType() === 'string') { $node['schema']['enum'] = $validator->getList(); $node['schema']['x-enum-name'] = $this->getEnumName($sdk->getNamespace() ?? '', $methodName, $name); $node['schema']['x-enum-keys'] = $this->getEnumKeys($sdk->getNamespace() ?? '', $methodName, $name); @@ -568,7 +599,35 @@ class OpenAPI3 extends Format break; case 'Appwrite\Utopia\Database\Validator\CompoundUID': $node['schema']['type'] = $validator->getType(); - $node['schema']['x-example'] = '[ID1:ID2]'; + $node['schema']['x-example'] = ''; + break; + case 'Appwrite\Utopia\Database\Validator\Operation': + if ($array) { + $validator = $validator->getValidator(); + } + + /** @var Operation $validator */ + $collectionIdKey = $validator->getCollectionIdKey(); + $documentIdKey = $validator->getDocumentIdKey(); + if ($array) { + $node['schema']['type'] = 'array'; + $node['schema']['items'] = ['type' => 'object']; + } else { + $node['schema']['type'] = 'object'; + } + $example = [ + 'action' => 'create', + 'databaseId' => '', + $collectionIdKey => '<'.\strtoupper(Template::fromCamelCaseToSnake($collectionIdKey)).'>', + $documentIdKey => '<'.\strtoupper(Template::fromCamelCaseToSnake($documentIdKey)).'>', + 'data' => [ + 'name' => 'Walter O\'Brien', + ], + ]; + if ($array) { + $example = [$example]; + } + $node['schema']['x-example'] = \str_replace("\n", "\n\t", \json_encode($example, JSON_PRETTY_PRINT)); break; default: $node['schema']['type'] = 'string'; diff --git a/src/Appwrite/SDK/Specification/Format/Swagger2.php b/src/Appwrite/SDK/Specification/Format/Swagger2.php index a923f40ffa..ffa74ecd22 100644 --- a/src/Appwrite/SDK/Specification/Format/Swagger2.php +++ b/src/Appwrite/SDK/Specification/Format/Swagger2.php @@ -8,6 +8,7 @@ use Appwrite\SDK\MethodType; use Appwrite\SDK\Response; use Appwrite\SDK\Specification\Format; use Appwrite\Template\Template; +use Appwrite\Utopia\Database\Validator\Operation; use Appwrite\Utopia\Response\Model; use Utopia\Database\Database; use Utopia\Database\Helpers\Permission; @@ -422,6 +423,17 @@ class Swagger2 extends Format $class = \get_class($validator); } + $array = false; + if ($class === 'Utopia\Validator\ArrayList') { + $array = true; + $subclass = \get_class($validator->getValidator()); + switch ($subclass) { + case 'Appwrite\Utopia\Database\Validator\Operation': + $class = $subclass; + break; + } + } + switch ($class) { case 'Utopia\Validator\Text': case 'Utopia\Database\Validator\UID': @@ -444,6 +456,20 @@ class Swagger2 extends Format $node['format'] = 'datetime'; $node['x-example'] = Model::TYPE_DATETIME_EXAMPLE; break; + case 'Utopia\Database\Validator\Spatial': + /** @var Spatial $validator */ + $node['type'] = 'array'; + $node['schema']['items'] = [ + 'oneOf' => [ + ['type' => 'array'] + ] + ]; + $node['x-example'] = match ($validator->getSpatialType()) { + Database::VAR_POINT => '[1, 2]', + Database::VAR_LINESTRING => '[[1, 2], [3, 4], [5, 6]]', + Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]', + }; + break; case 'Appwrite\Network\Validator\Email': $node['type'] = $validator->getType(); $node['format'] = 'email'; @@ -464,20 +490,6 @@ class Swagger2 extends Format 'type' => $validator->getValidator()->getType(), ]; break; - case 'Utopia\Database\Validator\Spatial': - /** @var Spatial $validator */ - $node['type'] = 'array'; - $node['schema']['items'] = [ - 'oneOf' => [ - ['type' => 'array'] - ] - ]; - $node['x-example'] = match ($validator->getSpatialType()) { - Database::VAR_POINT => '[1, 2]', - Database::VAR_LINESTRING => '[[1, 2], [3, 4], [5, 6]]', - Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]', - }; - break; case 'Utopia\Validator\JSON': case 'Utopia\Validator\Mock': case 'Utopia\Validator\Assoc': @@ -562,20 +574,47 @@ class Swagger2 extends Format break; } } - if ($allowed && $validator->getType() === 'string') { $node['enum'] = $validator->getList(); $node['x-enum-name'] = $this->getEnumName($namespace, $methodName, $name); $node['x-enum-keys'] = $this->getEnumKeys($namespace, $methodName, $name); } - if ($validator->getType() === 'integer') { $node['format'] = 'int32'; } break; case 'Appwrite\Utopia\Database\Validator\CompoundUID': $node['type'] = $validator->getType(); - $node['x-example'] = '[ID1:ID2]'; + $node['x-example'] = ''; + break; + case 'Appwrite\Utopia\Database\Validator\Operation': + if ($array) { + $validator = $validator->getValidator(); + } + + /** @var Operation $validator */ + $collectionIdKey = $validator->getCollectionIdKey(); + $documentIdKey = $validator->getDocumentIdKey(); + if ($array) { + $node['type'] = 'array'; + $node['collectionFormat'] = 'multi'; + $node['items'] = ['type' => 'object']; + } else { + $node['type'] = 'object'; + } + $example = [ + 'action' => 'create', + 'databaseId' => '', + $collectionIdKey => '<'.\strtoupper(Template::fromCamelCaseToSnake($collectionIdKey)).'>', + $documentIdKey => '<'.\strtoupper(Template::fromCamelCaseToSnake($documentIdKey)).'>', + 'data' => [ + 'name' => 'Walter O\'Brien', + ], + ]; + if ($array) { + $example = [$example]; + } + $node['x-example'] = \str_replace("\n", "\n\t", \json_encode($example, JSON_PRETTY_PRINT)); break; default: $node['type'] = 'string'; diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index f3fe2375b0..4632678940 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -64,12 +64,12 @@ class Operation extends Validator return $this->description; } - public function getCollectionIdName(): string + public function getCollectionIdKey(): string { return $this->collectionIdName; } - public function getDocumentIdName(): string + public function getDocumentIdKey(): string { return $this->documentIdName; } From ef685c5bc169307b0cf2dad6ad5477941d3784e3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 12 Sep 2025 00:19:39 +1200 Subject: [PATCH 146/385] Fix tests --- .../Databases/Legacy/DatabasesBase.php | 140 +++++------------- .../Legacy/Transactions/TransactionsTest.php | 2 +- 2 files changed, 34 insertions(+), 108 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 0616f982b1..ecf6cfc1ad 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4197,69 +4197,13 @@ trait DatabasesBase $this->assertCount(1, $documentsUser2['body']['documents']); } - public function testUniqueIndexDuplicate(): void + /** + * @depends testDefaultPermissions + */ + public function testUniqueIndexDuplicate(array $data): array { - // Setup: create database, collection, attribute, and initial document - $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'Unique Index DB', - ]); - $databaseId = $database['body']['$id']; - $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'permissions' => [ - Permission::create(Role::user(ID::custom($this->getUser()['$id']))), - Permission::read(Role::user(ID::custom($this->getUser()['$id']))), - Permission::update(Role::user(ID::custom($this->getUser()['$id']))), - Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), - ], - 'documentSecurity' => true, - ]); - $moviesId = $movies['body']['$id']; - $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); - sleep(2); - // Insert initial document - $doc1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Captain America', - 'releaseYear' => 1944, - 'actors' => [ - 'Chris Evans', - 'Samuel Jackson', - ] - ], - 'permissions' => [ - Permission::read(Role::user(ID::custom($this->getUser()['$id']))), - Permission::update(Role::user(ID::custom($this->getUser()['$id']))), - Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), - ] - ]); - $this->assertEquals(201, $doc1['headers']['status-code']); - - // Create unique index - $uniqueIndex = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/indexes', array_merge([ + $databaseId = $data['databaseId']; + $uniqueIndex = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -4268,11 +4212,13 @@ trait DatabasesBase 'type' => 'unique', 'attributes' => ['title'], ]); + $this->assertEquals(202, $uniqueIndex['headers']['status-code']); + sleep(2); - // test for failure (duplicate title) - $duplicate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ + // test for failure + $duplicate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -4291,10 +4237,11 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); + $this->assertEquals(409, $duplicate['headers']['status-code']); // Test for exception when updating document to conflict - $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -4313,9 +4260,11 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); + $this->assertEquals(201, $document['headers']['status-code']); - $duplicate = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $document['body']['$id'], array_merge([ + // Test for exception when updating document to conflict + $duplicate = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $document['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -4334,48 +4283,17 @@ trait DatabasesBase Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), ] ]); + $this->assertEquals(409, $duplicate['headers']['status-code']); + + return $data; } - public function testPersistentCreatedAt(): void + /** + * @depends testUniqueIndexDuplicate + */ + public function testPersistentCreatedAt(array $data): array { - // Setup: create database, collection, attribute - $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'CreatedAtDB', - ]); - $databaseId = $database['body']['$id']; - $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'permissions' => [ - Permission::create(Role::user(ID::custom($this->getUser()['$id']))), - Permission::read(Role::user(ID::custom($this->getUser()['$id']))), - Permission::update(Role::user(ID::custom($this->getUser()['$id']))), - Permission::delete(Role::user(ID::custom($this->getUser()['$id']))), - ], - 'documentSecurity' => true, - ]); - $moviesId = $movies['body']['$id']; - $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); - sleep(2); $headers = $this->getSide() === 'client' ? array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -4385,31 +4303,37 @@ trait DatabasesBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]; - $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', $headers, [ + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents', $headers, [ 'documentId' => ID::unique(), 'data' => [ 'title' => 'Creation Date Test', 'releaseYear' => 2000 ] ]); + $this->assertEquals($document['body']['title'], 'Creation Date Test'); + $documentId = $document['body']['$id']; $createdAt = $document['body']['$createdAt']; $updatedAt = $document['body']['$updatedAt']; \sleep(1); - $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $documentId, $headers, [ + + $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, $headers, [ 'data' => [ 'title' => 'Updated Date Test', ] ]); + $updatedAtSecond = $document['body']['$updatedAt']; + $this->assertEquals($document['body']['title'], 'Updated Date Test'); $this->assertEquals($document['body']['$createdAt'], $createdAt); $this->assertNotEquals($document['body']['$updatedAt'], $updatedAt); \sleep(1); - $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents/' . $documentId, $headers, [ + + $document = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, $headers, [ 'data' => [ 'title' => 'Again Updated Date Test', '$createdAt' => '2022-08-01 13:09:23.040', @@ -4426,6 +4350,8 @@ trait DatabasesBase $this->assertEquals($document['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); } + + return $data; } public function testUpdatePermissionsWithEmptyPayload(): array diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php index d2017c4a08..34eb561d63 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php @@ -954,7 +954,7 @@ class TransactionsTest extends Scope 'commit' => true ]); - $this->assertEquals(409, $response['headers']['status-code']); // Conflict + $this->assertEquals(404, $response['headers']['status-code']); // Conflict } /** From 09753cd6dc1af932615b095c4f8a31f09ef80c56 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 12 Sep 2025 01:16:23 +1200 Subject: [PATCH 147/385] Optimise schema --- app/config/collections/projects.php | 22 ++++------------------ src/Appwrite/Platform/Workers/Deletes.php | 5 ++--- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 7bd1d4a7d8..bf0cee3527 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2530,7 +2530,7 @@ return [ [ '$id' => ID::custom('status'), 'type' => Database::VAR_STRING, - 'size' => 16, // pending | committing | committed | rolled_back | failed + 'size' => 16, // pending | committing | committed | failed 'signed' => true, 'required' => false, 'default' => 'pending', @@ -2560,14 +2560,7 @@ return [ ], 'indexes' => [ [ - '$id' => ID::custom('_key_status'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['status'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_expires'), + '$id' => ID::custom('_key_expiresAt'), 'type' => Database::INDEX_KEY, 'attributes' => ['expiresAt'], 'lengths' => [], @@ -2624,7 +2617,7 @@ return [ [ '$id' => ID::custom('action'), 'type' => Database::VAR_STRING, - 'size' => 32, // create | update | upsert | increment | decrement | delete + 'size' => 32, // create | update | upsert | increment | decrement | delete | bulkCreate | bulkUpdate | bulkUpsert | bulkDelete 'signed' => true, 'required' => true, 'default' => null, @@ -2634,7 +2627,7 @@ return [ [ '$id' => ID::custom('data'), 'type' => Database::VAR_STRING, - 'size' => 65535, + 'size' => 5_000_000, // Allow large payloads for bulk operations 'signed' => false, 'required' => true, 'default' => null, @@ -2650,13 +2643,6 @@ return [ 'lengths' => [], 'orders' => [], ], - [ - '$id' => ID::custom('_key_internal_path'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['databaseInternalId', 'collectionInternalId'], - 'lengths' => [], - 'orders' => [], - ], ], ], ]; diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index cb6e843fd5..331a2668a3 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -1310,9 +1310,9 @@ class Deletes extends Action $dbForProject->deleteDocuments('transactionLogs', [ Query::equal('transactionInternalId', [$transactionInternalId]), ]); - Console::info("Transaction logs for {$transactionId} deleted."); + Console::info("Transaction logs for transaction {$transactionId} deleted."); } catch (Throwable $th) { - Console::error("Failed to delete transaction logs for {$transactionId}: " . $th->getMessage()); + Console::error("Failed to delete transaction logs for transaction {$transactionId}: " . $th->getMessage()); } } @@ -1323,7 +1323,6 @@ class Deletes extends Action try { $dbForProject->deleteDocuments('transactions', [ - Query::equal('status', ['pending']), Query::lessThan('expiresAt', DateTime::format(new \DateTime())), ], onNext: function (Document $transaction) use ($dbForProject, $project, &$transactionInternalIds) { $transactionInternalIds[] = $transaction->getSequence(); From 65ad2c9ff54cc97883ee0822e00e1585f7fad9b9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 12 Sep 2025 01:45:16 +1200 Subject: [PATCH 148/385] Handle usage + realtime --- .../Http/Databases/Transactions/Update.php | 147 ++++++++++++++++-- .../Http/TablesDB/Transactions/Update.php | 5 + .../Legacy/Transactions/TransactionsTest.php | 2 +- .../Transactions/TransactionsTest.php | 2 +- 4 files changed, 139 insertions(+), 17 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index ed70afee35..e8d3e6a1e4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -3,6 +3,8 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; use Appwrite\Event\Delete; +use Appwrite\Event\Event; +use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; @@ -63,6 +65,11 @@ class Update extends Action ->inject('response') ->inject('dbForProject') ->inject('queueForDeletes') + ->inject('queueForEvents') + ->inject('queueForStatsUsage') + ->inject('queueForRealtime') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') ->callback($this->action(...)); } @@ -73,6 +80,11 @@ class Update extends Action * @param UtopiaResponse $response * @param Database $dbForProject * @param Delete $queueForDeletes + * @param Event $queueForEvents + * @param StatsUsage $queueForStatsUsage + * @param Event $queueForRealtime + * @param Event $queueForFunctions + * @param Event $queueForWebhooks * @return void * @throws ConflictException * @throws Exception @@ -82,7 +94,7 @@ class Update extends Action * @throws Structure * @throws \Utopia\Exception */ - public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Delete $queueForDeletes): void + public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void { if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); @@ -106,13 +118,18 @@ class Update extends Action } if ($commit) { - $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, &$transaction) { + $operations = []; + + // Track metrics for usage stats + $totalOperations = 0; + $databaseOperations = []; + + $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', ])); - // Fetch operations ordered by sequence by default to - // replay operations in exact order they were created + // Fetch operations ordered by sequence by default to replay operations in exact order they were created $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), ]); @@ -130,6 +147,10 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; + // Track operations for stats + $totalOperations++; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + 1; + if ($data instanceof Document) { $data = $data->getArrayCopy(); } @@ -196,13 +217,99 @@ class Update extends Action throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); } }); + + $queueForStatsUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $totalOperations); + + // Add per-database metrics + foreach ($databaseOperations as $sequence => $count) { + $queueForStatsUsage->addMetric( + str_replace('{databaseInternalId}', $sequence, METRIC_DATABASE_ID_OPERATIONS_WRITES), + $count + ); + } + + // Trigger realtime events for each operation + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $action = $operation['action']; + $documentId = $operation['documentId']; + $data = $operation['data']; + + if ($data instanceof Document) { + $data = $data->getArrayCopy(); + } + + $database = $dbForProject->findOne('databases', [ + Query::equal('$sequence', [$databaseInternalId]) + ]); + $collection = $dbForProject->findOne('database_' . $databaseInternalId, [ + Query::equal('$sequence', [$collectionInternalId]) + ]); + + $queueForEvents + ->setParam('databaseId', $database->getId()) + ->setContext('database', $database) + ->setParam('collectionId', $collection->getId()) + ->setParam('tableId', $collection->getId()) + ->setContext('collection', $collection); + + $eventAction = ''; + $documents = []; + + switch ($action) { + case 'create': + $eventAction = 'create'; + $documents[] = $documentId ?? $data['$id'] ?? null; + break; + case 'update': + case 'increment': + case 'decrement': + $eventAction = 'update'; + $documents[] = $documentId; + break; + case 'delete': + $eventAction = 'delete'; + $documents[] = $documentId; + break; + case 'upsert': + $eventAction = 'upsert'; + $documents[] = $documentId ?? $data['$id'] ?? null; + break; + case 'bulkCreate': + case 'bulkUpdate': + case 'bulkUpsert': + case 'bulkDelete': + break; + } + + // Trigger events for each document + foreach ($documents as $docId) { + if ($docId) { + $queueForEvents + ->setParam('documentId', $docId) + ->setParam('rowId', $docId) + ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].' . $eventAction); + + $queueForRealtime->from($queueForEvents)->trigger(); + $queueForFunctions->from($queueForEvents)->trigger(); + $queueForWebhooks->from($queueForEvents)->trigger(); + + $queueForEvents->reset(); + $queueForRealtime->reset(); + $queueForFunctions->reset(); + $queueForWebhooks->reset(); + } + } + } } if ($rollback) { $transaction = $dbForProject->updateDocument( 'transactions', $transactionId, - new Document(['status' => 'rolledBack']) + new Document(['status' => 'failed']) ); $queueForDeletes @@ -226,7 +333,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { if ($documentId && !isset($data['$id'])) { $data['$id'] = $documentId; } @@ -250,7 +358,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -288,7 +397,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -318,7 +428,8 @@ class Update extends Action string $documentId, \DateTime $createdAt, array &$state - ): void { + ): void + { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -350,7 +461,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -387,7 +499,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -423,7 +536,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { $dbForProject->createDocuments( $collectionId, @@ -447,7 +561,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->updateDocuments( @@ -481,7 +596,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { // Run bulk upsert without timestamp wrapper, checking manually in callback $dbForProject->upsertDocuments( $collectionId, @@ -517,7 +633,8 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): void + { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->deleteDocuments( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index b754ec97a1..d76de0766d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -53,6 +53,11 @@ class Update extends TransactionsUpdate ->inject('response') ->inject('dbForProject') ->inject('queueForDeletes') + ->inject('queueForEvents') + ->inject('queueForStatsUsage') + ->inject('queueForRealtime') + ->inject('queueForFunctions') + ->inject('queueForWebhooks') ->callback($this->action(...)); } } diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php index 34eb561d63..02e3082626 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php @@ -485,7 +485,7 @@ class TransactionsTest extends Scope ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('rolledBack', $response['body']['status']); + $this->assertEquals('failed', $response['body']['status']); // Verify no documents were created $documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index 9ec5994903..30bb4cb290 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -485,7 +485,7 @@ class TransactionsTest extends Scope ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('rolledBack', $response['body']['status']); + $this->assertEquals('failed', $response['body']['status']); // Verify no rows were created $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ From 38dc92bfa792536d202eea56b314234840d16357 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 12 Sep 2025 02:05:19 +1200 Subject: [PATCH 149/385] Delete with worker --- .../Http/Databases/Transactions/Delete.php | 15 +++++----- .../Http/Databases/Transactions/Update.php | 30 +++++++------------ 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php index 43c0c5c4a8..da92ce1b4c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; +use Appwrite\Event\Delete as DeleteEvent; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; @@ -9,7 +10,6 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; -use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; @@ -51,10 +51,11 @@ class Delete extends Action ->param('transactionId', '', new UID(), 'Transaction ID.') ->inject('response') ->inject('dbForProject') + ->inject('queueForDeletes') ->callback($this->action(...)); } - public function action(string $transactionId, UtopiaResponse $response, Database $dbForProject): void + public function action(string $transactionId, UtopiaResponse $response, Database $dbForProject, DeleteEvent $queueForDeletes): void { $transaction = $dbForProject->getDocument('transactions', $transactionId); @@ -62,13 +63,11 @@ class Delete extends Action throw new Exception(Exception::TRANSACTION_NOT_FOUND); } - if (!$dbForProject->deleteDocument('transactions', $transactionId)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove transaction from DB'); - } + $dbForProject->deleteDocument('transactions', $transactionId); - $dbForProject->deleteDocuments('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - ]); + $queueForDeletes + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($transaction); $response->noContent(); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index e8d3e6a1e4..54b339a46c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -333,8 +333,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { if ($documentId && !isset($data['$id'])) { $data['$id'] = $documentId; } @@ -358,8 +357,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -397,8 +395,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -428,8 +425,7 @@ class Update extends Action string $documentId, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -461,8 +457,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -499,8 +494,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { @@ -536,8 +530,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { $dbForProject->createDocuments( $collectionId, @@ -561,8 +554,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->updateDocuments( @@ -596,8 +588,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { // Run bulk upsert without timestamp wrapper, checking manually in callback $dbForProject->upsertDocuments( $collectionId, @@ -633,8 +624,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void - { + ): void { $queries = Query::parseQueries($data['queries'] ?? []); $dbForProject->deleteDocuments( From 7120bce761aa3401d5065e7c3bf9eb6f2dc705a7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 12 Sep 2025 21:47:40 +1200 Subject: [PATCH 150/385] Handle structure exception --- .../Http/Databases/Transactions/Operations/Create.php | 2 +- .../Databases/Http/Databases/Transactions/Update.php | 7 ++++++- .../Http/TablesDB/Transactions/Operations/Create.php | 2 +- .../Databases/Legacy/Transactions/TransactionsTest.php | 2 +- .../Databases/TablesDB/Transactions/TransactionsTest.php | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index a33af05e3d..d2e438706a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -36,7 +36,7 @@ class Create extends Action $this ->setHttpMethod(self::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/databases/transactions/:transactionId/operations') - ->desc('Add operations to transaction') + ->desc('Create operations scoped to a transaction') ->groups(['api', 'database', 'transactions']) ->label('scope', 'transactions.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 54b339a46c..2c555d433c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -17,7 +17,7 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\NotFound as NotFoundException; -use Utopia\Database\Exception\Structure; +use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Query; use Utopia\Database\Validator\UID; @@ -210,6 +210,11 @@ class Update extends Action 'status' => 'failed', ])); throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); + } catch (StructureException $e) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); } catch (TransactionException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index 6b2ee2ce4c..2280a6f7e3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -30,7 +30,7 @@ class Create extends OperationsCreate $this ->setHttpMethod(self::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/tablesdb/transactions/:transactionId/operations') - ->desc('Add operations to transaction') + ->desc('Create operations scoped to a transaction') ->groups(['api', 'database', 'transactions']) ->label('scope', 'transactions.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php index 02e3082626..9d8cb7961e 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php @@ -96,7 +96,7 @@ class TransactionsTest extends Scope /** * Test adding operations to a transaction */ - public function testAddOperations(): void + public function testCreateOperations(): void { // Create database first $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index 30bb4cb290..68f0afb835 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -96,7 +96,7 @@ class TransactionsTest extends Scope /** * Test adding operations to a transaction */ - public function testAddOperations(): void + public function testCreateOperations(): void { // Create database first $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ From 4f916fae220e30fe3bbe65fbfd35e7fa2d762847 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Sep 2025 00:04:37 +1200 Subject: [PATCH 151/385] Handle limit exception --- .../Databases/Http/Databases/Transactions/Update.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 2c555d433c..4509114bfc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -16,6 +16,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Transaction as TransactionException; @@ -215,6 +216,11 @@ class Update extends Action 'status' => 'failed', ])); throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (LimitException $e) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage()); } catch (TransactionException $e) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', From 304ebf12b91bdaf38e61054ea779b0aa666d2bea Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Sep 2025 00:04:49 +1200 Subject: [PATCH 152/385] Add more tests --- .../Legacy/Transactions/TransactionsTest.php | 596 ++++++++++++++++++ .../Transactions/TransactionsTest.php | 596 ++++++++++++++++++ 2 files changed, 1192 insertions(+) diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php index 9d8cb7961e..d712c2ca6c 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php @@ -3635,4 +3635,600 @@ class TransactionsTest extends Scope $this->assertEquals('active', $response['body']['status']); } } + + /** + * Test increment and decrement operations in transaction + */ + public function testIncrementDecrementOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IncrementDecrementTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'CounterCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add integer attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'default' => 0, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'score', + 'required' => false, + 'default' => 100, + ]); + + sleep(2); + + // Create initial document + $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'counter_doc', + 'data' => [ + 'counter' => 10, + 'score' => 50 + ] + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add increment and decrement operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'increment', + 'documentId' => 'counter_doc', + 'data' => [ + 'attribute' => 'counter', + 'value' => 5, + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'decrement', + 'documentId' => 'counter_doc', + 'data' => [ + 'attribute' => 'score', + 'value' => 20, + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'increment', + 'documentId' => 'counter_doc', + 'data' => [ + 'attribute' => 'counter', + 'value' => 3, + 'max' => 20 + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'decrement', + 'documentId' => 'counter_doc', + 'data' => [ + 'attribute' => 'score', + 'value' => 30, + 'min' => 0 + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify final values + $doc = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/counter_doc", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $doc['headers']['status-code']); + // counter: 10 + 5 + 3 = 18 (capped at 20 max) + $this->assertEquals(18, $doc['body']['counter']); + // score: 50 - 20 - 100 = -70, but min is 0 + $this->assertEquals(0, $doc['body']['score']); + } + + /** + * Test bulk update operations in transaction + */ + public function testBulkUpdateOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'BulkUpdateCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + // Create initial documents + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => "doc_{$i}", + 'data' => [ + 'status' => 'pending', + 'category' => $i % 2 === 0 ? 'even' : 'odd' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk update operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [ + Query::equal('category', ['even'])->toString() + ], + 'data' => [ + 'status' => 'approved' + ] + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [ + Query::equal('category', ['odd'])->toString() + ], + 'data' => [ + 'status' => 'rejected' + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify updates + $docs = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + foreach ($docs['body']['documents'] as $doc) { + if ($doc['category'] === 'even') { + $this->assertEquals('approved', $doc['status']); + } else { + $this->assertEquals('rejected', $doc['status']); + } + } + } + + /** + * Test bulk upsert operations in transaction + */ + public function testBulkUpsertOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpsertTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'BulkUpsertCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 100, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'required' => false, + ]); + + sleep(2); + + // Create some initial documents + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'existing_1', + 'data' => [ + 'name' => 'Existing Document 1', + 'value' => 10 + ] + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'existing_2', + 'data' => [ + 'name' => 'Existing Document 2', + 'value' => 20 + ] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk upsert operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkUpsert', + 'data' => [ + [ + '$id' => 'existing_1', + 'name' => 'Updated Document 1', + 'value' => 100 + ], + [ + '$id' => 'new_1', + 'name' => 'New Document 1', + 'value' => 30 + ], + [ + '$id' => 'new_2', + 'name' => 'New Document 2', + 'value' => 40 + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify results + $docs = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(4, $docs['body']['total']); + + $docMap = []; + foreach ($docs['body']['documents'] as $doc) { + $docMap[$doc['$id']] = $doc; + } + + // Verify updated document + $this->assertEquals('Updated Document 1', $docMap['existing_1']['name']); + $this->assertEquals(100, $docMap['existing_1']['value']); + + // Verify unchanged document + $this->assertEquals('Existing Document 2', $docMap['existing_2']['name']); + $this->assertEquals(20, $docMap['existing_2']['value']); + + // Verify new documents + $this->assertEquals('New Document 1', $docMap['new_1']['name']); + $this->assertEquals(30, $docMap['new_1']['value']); + $this->assertEquals('New Document 2', $docMap['new_2']['name']); + $this->assertEquals(40, $docMap['new_2']['value']); + } + + /** + * Test bulk delete operations in transaction + */ + public function testBulkDeleteOperations(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'BulkDeleteCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add attributes + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 50, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + ]); + + sleep(2); + + // Create initial documents + for ($i = 1; $i <= 10; $i++) { + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => "doc_{$i}", + 'data' => [ + 'type' => $i <= 5 ? 'temp' : 'permanent', + 'priority' => $i + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk delete operations + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [ + Query::equal('type', ['temp'])->toString() + ] + ] + ], + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [ + Query::greaterThan('priority', 8)->toString() + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify deletions + $docs = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have deleted docs 1-5 (temp) and docs 9-10 (priority > 8) + // Remaining should be docs 6-8 + $this->assertEquals(3, $docs['body']['total']); + + $remainingIds = array_map(fn($doc) => $doc['$id'], $docs['body']['documents']); + sort($remainingIds); + $this->assertEquals(['doc_6', 'doc_7', 'doc_8'], $remainingIds); + } } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index 68f0afb835..4466d53e30 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -3635,4 +3635,600 @@ class TransactionsTest extends Scope $this->assertEquals('active', $response['body']['status']); } } + + /** + * Test increment and decrement operations in transaction + */ + public function testIncrementDecrementOperations(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IncrementDecrementTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'CounterTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add integer columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'default' => 0, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'score', + 'required' => false, + 'default' => 100, + ]); + + sleep(2); + + // Create initial row + $row = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'counter_row', + 'data' => [ + 'counter' => 10, + 'score' => 50 + ] + ]); + + $this->assertEquals(201, $row['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add increment and decrement operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'increment', + 'rowId' => 'counter_row', + 'data' => [ + 'column' => 'counter', + 'value' => 5, + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'decrement', + 'rowId' => 'counter_row', + 'data' => [ + 'column' => 'score', + 'value' => 20, + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'increment', + 'rowId' => 'counter_row', + 'data' => [ + 'column' => 'counter', + 'value' => 3, + 'max' => 20 + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'decrement', + 'rowId' => 'counter_row', + 'data' => [ + 'column' => 'score', + 'value' => 30, + 'min' => 0 + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify final values + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/counter_row", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $row['headers']['status-code']); + // counter: 10 + 5 + 3 = 18 (capped at 20 max) + $this->assertEquals(18, $row['body']['counter']); + // score: 50 - 20 - 100 = -70, but min is 0 + $this->assertEquals(0, $row['body']['score']); + } + + /** + * Test bulk update operations in transaction + */ + public function testBulkUpdateOperations(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'BulkUpdateTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + // Create initial rows + for ($i = 1; $i <= 5; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => "row_{$i}", + 'data' => [ + 'status' => 'pending', + 'category' => $i % 2 === 0 ? 'even' : 'odd' + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk update operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [ + Query::equal('category', ['even'])->toString() + ], + 'data' => [ + 'status' => 'approved' + ] + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [ + Query::equal('category', ['odd'])->toString() + ], + 'data' => [ + 'status' => 'rejected' + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify updates + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + foreach ($rows['body']['rows'] as $row) { + if ($row['category'] === 'even') { + $this->assertEquals('approved', $row['status']); + } else { + $this->assertEquals('rejected', $row['status']); + } + } + } + + /** + * Test bulk upsert operations in transaction + */ + public function testBulkUpsertOperations(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpsertTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'BulkUpsertTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 100, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'required' => false, + ]); + + sleep(2); + + // Create some initial rows + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'existing_1', + 'data' => [ + 'name' => 'Existing Row 1', + 'value' => 10 + ] + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'existing_2', + 'data' => [ + 'name' => 'Existing Row 2', + 'value' => 20 + ] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk upsert operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkUpsert', + 'data' => [ + [ + '$id' => 'existing_1', + 'name' => 'Updated Row 1', + 'value' => 100 + ], + [ + '$id' => 'new_1', + 'name' => 'New Row 1', + 'value' => 30 + ], + [ + '$id' => 'new_2', + 'name' => 'New Row 2', + 'value' => 40 + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify results + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(4, $rows['body']['total']); + + $rowMap = []; + foreach ($rows['body']['rows'] as $row) { + $rowMap[$row['$id']] = $row; + } + + // Verify updated row + $this->assertEquals('Updated Row 1', $rowMap['existing_1']['name']); + $this->assertEquals(100, $rowMap['existing_1']['value']); + + // Verify unchanged row + $this->assertEquals('Existing Row 2', $rowMap['existing_2']['name']); + $this->assertEquals(20, $rowMap['existing_2']['value']); + + // Verify new rows + $this->assertEquals('New Row 1', $rowMap['new_1']['name']); + $this->assertEquals(30, $rowMap['new_1']['value']); + $this->assertEquals('New Row 2', $rowMap['new_2']['name']); + $this->assertEquals(40, $rowMap['new_2']['value']); + } + + /** + * Test bulk delete operations in transaction + */ + public function testBulkDeleteOperations(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'BulkDeleteTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 50, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + ]); + + sleep(2); + + // Create initial rows + for ($i = 1; $i <= 10; $i++) { + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => "row_{$i}", + 'data' => [ + 'type' => $i <= 5 ? 'temp' : 'permanent', + 'priority' => $i + ] + ]); + } + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $transactionId = $transaction['body']['$id']; + + // Add bulk delete operations + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [ + Query::equal('type', ['temp'])->toString() + ] + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [ + Query::greaterThan('priority', 8)->toString() + ] + ] + ] + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify deletions + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Should have deleted rows 1-5 (temp) and rows 9-10 (priority > 8) + // Remaining should be rows 6-8 + $this->assertEquals(3, $rows['body']['total']); + + $remainingIds = array_map(fn($row) => $row['$id'], $rows['body']['rows']); + sort($remainingIds); + $this->assertEquals(['row_6', 'row_7', 'row_8'], $remainingIds); + } } From e5a95ed990ec8160a49a9da5292f584bae6dc822 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Sep 2025 00:31:35 +1200 Subject: [PATCH 153/385] Update DB --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index ba8b58fcea..c532f24ef2 100644 --- a/composer.lock +++ b/composer.lock @@ -3638,16 +3638,16 @@ }, { "name": "utopia-php/database", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "44af82dcb44cdaa4b2d7f30528903f186a972ee0" + "reference": "e68f2e358bdfbc682aaad4956d325535664ec7d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/44af82dcb44cdaa4b2d7f30528903f186a972ee0", - "reference": "44af82dcb44cdaa4b2d7f30528903f186a972ee0", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e68f2e358bdfbc682aaad4956d325535664ec7d5", + "reference": "e68f2e358bdfbc682aaad4956d325535664ec7d5", "shasum": "" }, "require": { @@ -3688,9 +3688,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.0.1" + "source": "https://github.com/utopia-php/database/tree/2.0.2" }, - "time": "2025-09-11T08:33:25+00:00" + "time": "2025-09-12T12:29:38+00:00" }, { "name": "utopia-php/detector", From 5c09e8ae00f19b203cd9ce907066fad14b8581ea Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Sep 2025 00:35:03 +1200 Subject: [PATCH 154/385] Lint --- .../Services/Databases/Legacy/Transactions/TransactionsTest.php | 2 +- .../Databases/TablesDB/Transactions/TransactionsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php index d712c2ca6c..a76d909b5e 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php @@ -4227,7 +4227,7 @@ class TransactionsTest extends Scope // Remaining should be docs 6-8 $this->assertEquals(3, $docs['body']['total']); - $remainingIds = array_map(fn($doc) => $doc['$id'], $docs['body']['documents']); + $remainingIds = array_map(fn ($doc) => $doc['$id'], $docs['body']['documents']); sort($remainingIds); $this->assertEquals(['doc_6', 'doc_7', 'doc_8'], $remainingIds); } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php index 4466d53e30..910b675afc 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsTest.php @@ -4227,7 +4227,7 @@ class TransactionsTest extends Scope // Remaining should be rows 6-8 $this->assertEquals(3, $rows['body']['total']); - $remainingIds = array_map(fn($row) => $row['$id'], $rows['body']['rows']); + $remainingIds = array_map(fn ($row) => $row['$id'], $rows['body']['rows']); sort($remainingIds); $this->assertEquals(['row_6', 'row_7', 'row_8'], $remainingIds); } From 0f9c2001f7a320e20e606db737f9496c4daa6c06 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Sep 2025 02:42:42 +1200 Subject: [PATCH 155/385] Demo SDK --- app/config/platforms.php | 8 +++---- app/config/specs/open-api3-1.8.x-client.json | 4 ++-- app/config/specs/open-api3-1.8.x-console.json | 4 ++-- app/config/specs/open-api3-1.8.x-server.json | 4 ++-- app/config/specs/open-api3-latest-client.json | 8 +++---- .../specs/open-api3-latest-console.json | 8 +++---- app/config/specs/open-api3-latest-server.json | 8 +++---- app/config/specs/swagger2-1.8.x-client.json | 4 ++-- app/config/specs/swagger2-1.8.x-console.json | 4 ++-- app/config/specs/swagger2-1.8.x-server.json | 4 ++-- app/config/specs/swagger2-latest-client.json | 8 +++---- app/config/specs/swagger2-latest-console.json | 8 +++---- app/config/specs/swagger2-latest-server.json | 8 +++---- .../examples/databases/create-document.md | 3 ++- .../examples/databases/create-operations.md | 24 +++++++++++++++++++ .../examples/databases/create-transaction.md | 13 ++++++++++ .../databases/decrement-document-attribute.md | 3 ++- .../examples/databases/delete-document.md | 3 ++- .../examples/databases/delete-transaction.md | 13 ++++++++++ .../examples/databases/get-document.md | 3 ++- .../examples/databases/get-transaction.md | 13 ++++++++++ .../databases/increment-document-attribute.md | 3 ++- .../examples/databases/list-documents.md | 3 ++- .../examples/databases/list-transactions.md | 13 ++++++++++ .../examples/databases/update-document.md | 3 ++- .../examples/databases/update-transaction.md | 15 ++++++++++++ .../examples/databases/upsert-document.md | 3 ++- .../examples/tablesdb/create-operations.md | 24 +++++++++++++++++++ .../examples/tablesdb/create-row.md | 3 ++- .../examples/tablesdb/create-transaction.md | 13 ++++++++++ .../examples/tablesdb/decrement-row-column.md | 3 ++- .../examples/tablesdb/delete-row.md | 3 ++- .../examples/tablesdb/delete-transaction.md | 13 ++++++++++ .../client-web/examples/tablesdb/get-row.md | 3 ++- .../examples/tablesdb/get-transaction.md | 13 ++++++++++ .../examples/tablesdb/increment-row-column.md | 3 ++- .../client-web/examples/tablesdb/list-rows.md | 3 ++- .../examples/tablesdb/list-transactions.md | 13 ++++++++++ .../examples/tablesdb/update-row.md | 3 ++- .../examples/tablesdb/update-transaction.md | 15 ++++++++++++ .../examples/tablesdb/upsert-row.md | 3 ++- .../examples/databases/create-document.md | 3 ++- .../examples/databases/create-documents.md | 3 ++- .../examples/databases/create-operations.md | 23 ++++++++++++++++++ .../examples/databases/create-transaction.md | 12 ++++++++++ .../databases/decrement-document-attribute.md | 3 ++- .../examples/databases/delete-document.md | 3 ++- .../examples/databases/delete-documents.md | 3 ++- .../examples/databases/delete-transaction.md | 12 ++++++++++ .../examples/databases/get-document.md | 3 ++- .../examples/databases/get-transaction.md | 12 ++++++++++ .../databases/increment-document-attribute.md | 3 ++- .../examples/databases/list-documents.md | 3 ++- .../examples/databases/list-transactions.md | 12 ++++++++++ .../examples/databases/update-document.md | 3 ++- .../examples/databases/update-documents.md | 3 ++- .../examples/databases/update-transaction.md | 14 +++++++++++ .../examples/databases/upsert-document.md | 3 ++- .../examples/databases/upsert-documents.md | 3 ++- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 23 ++++++++++++++++++ .../examples/tablesdb/create-row.md | 3 ++- .../examples/tablesdb/create-rows.md | 3 ++- .../examples/tablesdb/create-transaction.md | 12 ++++++++++ .../examples/tablesdb/decrement-row-column.md | 3 ++- .../examples/tablesdb/delete-row.md | 3 ++- .../examples/tablesdb/delete-rows.md | 3 ++- .../examples/tablesdb/delete-transaction.md | 12 ++++++++++ .../examples/tablesdb/get-row.md | 3 ++- .../examples/tablesdb/get-transaction.md | 12 ++++++++++ .../examples/tablesdb/increment-row-column.md | 3 ++- .../examples/tablesdb/list-rows.md | 3 ++- .../examples/tablesdb/list-transactions.md | 12 ++++++++++ .../examples/tablesdb/update-row.md | 3 ++- .../examples/tablesdb/update-rows.md | 3 ++- .../examples/tablesdb/update-transaction.md | 14 +++++++++++ .../examples/tablesdb/upsert-row.md | 3 ++- .../examples/tablesdb/upsert-rows.md | 3 ++- 79 files changed, 474 insertions(+), 82 deletions(-) create mode 100644 docs/examples/1.8.x/client-web/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-web/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-web/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-web/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-transaction.md diff --git a/app/config/platforms.php b/app/config/platforms.php index 7623bb896e..5cc62d6e23 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '20.0.0', + 'version' => '20.1.0-rc.1', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -24,7 +24,7 @@ return [ 'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git', 'gitRepoName' => 'sdk-for-web', 'gitUserName' => 'appwrite', - 'gitBranch' => 'dev', + 'gitBranch' => 'feat-txn', 'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'), 'demos' => [ [ @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '19.0.0', + 'version' => '19.1.0-rc.1', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -275,7 +275,7 @@ return [ 'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git', 'gitRepoName' => 'sdk-for-node', 'gitUserName' => 'appwrite', - 'gitBranch' => 'dev', + 'gitBranch' => 'feat-txn', 'changelog' => \realpath(__DIR__ . '/../../docs/sdks/nodejs/CHANGELOG.md'), ], [ diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index e94ac895b6..6d5acf8c86 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -5143,7 +5143,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -8285,7 +8285,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 1ff651107b..797a7e72e4 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -5542,7 +5542,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -33677,7 +33677,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 987ae7fece..8e8c33ace4 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -5090,7 +5090,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -24203,7 +24203,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index a24d36fe76..6d5acf8c86 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -5143,7 +5143,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -5212,7 +5212,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -8285,7 +8285,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" @@ -8354,7 +8354,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 3316be2f8b..797a7e72e4 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -5542,7 +5542,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -5611,7 +5611,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -33677,7 +33677,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" @@ -33746,7 +33746,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index a2d91def99..8e8c33ace4 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -5090,7 +5090,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -5161,7 +5161,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -24203,7 +24203,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" @@ -24274,7 +24274,7 @@ "operations": { "type": "array", "description": "Array of staged operations.", - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 7297412924..e87ce88a6c 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -5282,7 +5282,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -8359,7 +8359,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 5e8aad66e4..16744679ec 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -5701,7 +5701,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -33793,7 +33793,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 9dd44734bb..d3f7129c95 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -5237,7 +5237,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -24375,7 +24375,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 4d0e75c6fa..e87ce88a6c 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -5282,7 +5282,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -5350,7 +5350,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -8359,7 +8359,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" @@ -8427,7 +8427,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index c39987cbaf..16744679ec 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -5701,7 +5701,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -5769,7 +5769,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -33793,7 +33793,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" @@ -33861,7 +33861,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 7e0785fc0a..d3f7129c95 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -5237,7 +5237,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -5307,7 +5307,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"collectionId\": \"\",\n \"documentId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"collectionId\": \"\",\n\t \"documentId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } @@ -24375,7 +24375,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Add operations to transaction", + "summary": "Create operations scoped to a transaction", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" @@ -24445,7 +24445,7 @@ "type": "array", "description": "Array of staged operations.", "default": [], - "x-example": "[\n {\n \"action\": \"create\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"name\": \"Walter O'Brien\"\n }\n }\n]", + "x-example": "[\n\t {\n\t \"action\": \"create\",\n\t \"databaseId\": \"\",\n\t \"tableId\": \"\",\n\t \"rowId\": \"\",\n\t \"data\": {\n\t \"name\": \"Walter O'Brien\"\n\t }\n\t }\n\t]", "items": { "type": "object" } diff --git a/docs/examples/1.8.x/client-web/examples/databases/create-document.md b/docs/examples/1.8.x/client-web/examples/databases/create-document.md index 08606c9b4f..8417575ebf 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-web/examples/databases/create-document.md @@ -17,7 +17,8 @@ const result = await databases.createDocument({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/create-operations.md b/docs/examples/1.8.x/client-web/examples/databases/create-operations.md new file mode 100644 index 0000000000..2ebc085d44 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/create-operations.md @@ -0,0 +1,24 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-web/examples/databases/create-transaction.md new file mode 100644 index 0000000000..5371412cc9 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-web/examples/databases/decrement-document-attribute.md index 98629c4e6c..f8e0ae9829 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-web/examples/databases/decrement-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.decrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/delete-document.md b/docs/examples/1.8.x/client-web/examples/databases/delete-document.md index 4192085653..4d10afdac5 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-web/examples/databases/delete-document.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.deleteDocument({ databaseId: '', collectionId: '', - documentId: '' + documentId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-web/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..afe55c547a --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/get-document.md b/docs/examples/1.8.x/client-web/examples/databases/get-document.md index b3a7558907..5a44aeb73e 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/get-document.md +++ b/docs/examples/1.8.x/client-web/examples/databases/get-document.md @@ -10,7 +10,8 @@ const result = await databases.getDocument({ databaseId: '', collectionId: '', documentId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-web/examples/databases/get-transaction.md new file mode 100644 index 0000000000..cc51199a76 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-web/examples/databases/increment-document-attribute.md index 8adb5d8091..eaf718e98d 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-web/examples/databases/increment-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.incrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/list-documents.md b/docs/examples/1.8.x/client-web/examples/databases/list-documents.md index fb1d508fed..ece656a644 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/client-web/examples/databases/list-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.listDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-web/examples/databases/list-transactions.md new file mode 100644 index 0000000000..f2ce1f7536 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/update-document.md b/docs/examples/1.8.x/client-web/examples/databases/update-document.md index bf3554812d..33a6d73a12 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-web/examples/databases/update-document.md @@ -11,7 +11,8 @@ const result = await databases.updateDocument({ collectionId: '', documentId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-web/examples/databases/update-transaction.md new file mode 100644 index 0000000000..9274b0f9bf --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/databases/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, Databases } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-web/examples/databases/upsert-document.md index c56bc5534d..e14ad5fc6b 100644 --- a/docs/examples/1.8.x/client-web/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-web/examples/databases/upsert-document.md @@ -11,7 +11,8 @@ const result = await databases.upsertDocument({ collectionId: '', documentId: '', data: {}, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-web/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..c25b051430 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-web/examples/tablesdb/create-row.md index 1dd1fe4241..3bbcf89d4f 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/create-row.md @@ -17,7 +17,8 @@ const result = await tablesDB.createRow({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-web/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..17787dc9a3 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-web/examples/tablesdb/decrement-row-column.md index 59f66d973f..d6c64645f3 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/decrement-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.decrementRowColumn({ rowId: '', column: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-web/examples/tablesdb/delete-row.md index 637114d413..54c005c702 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/delete-row.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.deleteRow({ databaseId: '', tableId: '', - rowId: '' + rowId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-web/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..2ff1198225 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/get-row.md b/docs/examples/1.8.x/client-web/examples/tablesdb/get-row.md index 4e436432b7..b345d145aa 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/get-row.md @@ -10,7 +10,8 @@ const result = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-web/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..8e2f24cd4c --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-web/examples/tablesdb/increment-row-column.md index a7f3a8c312..5baca80c35 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/increment-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.incrementRowColumn({ rowId: '', column: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/client-web/examples/tablesdb/list-rows.md index 63149aaba4..c0efd8486c 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/list-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.listRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-web/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..fbf0908a81 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-web/examples/tablesdb/update-row.md index 1dba006762..ecbcd4fc7a 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/update-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.updateRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-web/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..2d987e4235 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-web/examples/tablesdb/upsert-row.md index 1add1c45b9..ddac9ff327 100644 --- a/docs/examples/1.8.x/client-web/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-web/examples/tablesdb/upsert-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.upsertRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/create-document.md b/docs/examples/1.8.x/server-nodejs/examples/databases/create-document.md index 175e06301e..6fe77c42be 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/create-document.md @@ -18,5 +18,6 @@ const result = await databases.createDocument({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/create-documents.md b/docs/examples/1.8.x/server-nodejs/examples/databases/create-documents.md index 73d08aa21e..8815d8d90f 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/create-documents.md @@ -10,5 +10,6 @@ const databases = new sdk.Databases(client); const result = await databases.createDocuments({ databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/create-operations.md b/docs/examples/1.8.x/server-nodejs/examples/databases/create-operations.md new file mode 100644 index 0000000000..da8452e3db --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/create-operations.md @@ -0,0 +1,23 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/databases/create-transaction.md new file mode 100644 index 0000000000..f3da2919f8 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.createTransaction({ + ttl: 60 // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-nodejs/examples/databases/decrement-document-attribute.md index 87e4d7d25c..c01b250b08 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/decrement-document-attribute.md @@ -13,5 +13,6 @@ const result = await databases.decrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/delete-document.md b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-document.md index 429554b74b..bfc19777f9 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-document.md @@ -10,5 +10,6 @@ const databases = new sdk.Databases(client); const result = await databases.deleteDocument({ databaseId: '', collectionId: '', - documentId: '' + documentId: '', + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-documents.md index 0965d8ddaf..9440d20999 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-documents.md @@ -10,5 +10,6 @@ const databases = new sdk.Databases(client); const result = await databases.deleteDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..53d676e74c --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/delete-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.deleteTransaction({ + transactionId: '' +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/get-document.md b/docs/examples/1.8.x/server-nodejs/examples/databases/get-document.md index 7cc71cd0c3..7abea4e44e 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/get-document.md @@ -11,5 +11,6 @@ const result = await databases.getDocument({ databaseId: '', collectionId: '', documentId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/databases/get-transaction.md new file mode 100644 index 0000000000..9b7297c7e7 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/get-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.getTransaction({ + transactionId: '' +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-nodejs/examples/databases/increment-document-attribute.md index 2b47f5a75d..843d163bca 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/increment-document-attribute.md @@ -13,5 +13,6 @@ const result = await databases.incrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/list-documents.md b/docs/examples/1.8.x/server-nodejs/examples/databases/list-documents.md index 148bf83c8f..7405f3e28d 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/list-documents.md @@ -10,5 +10,6 @@ const databases = new sdk.Databases(client); const result = await databases.listDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-nodejs/examples/databases/list-transactions.md new file mode 100644 index 0000000000..9a36eb0f93 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/list-transactions.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.listTransactions({ + queries: [] // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/update-document.md b/docs/examples/1.8.x/server-nodejs/examples/databases/update-document.md index 8a9a6856b4..3e953760a1 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/update-document.md @@ -12,5 +12,6 @@ const result = await databases.updateDocument({ collectionId: '', documentId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/update-documents.md b/docs/examples/1.8.x/server-nodejs/examples/databases/update-documents.md index 2cfb8c7800..dc79e433aa 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/update-documents.md @@ -11,5 +11,6 @@ const result = await databases.updateDocuments({ databaseId: '', collectionId: '', data: {}, // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/databases/update-transaction.md new file mode 100644 index 0000000000..57654495ba --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/update-transaction.md @@ -0,0 +1,14 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const databases = new sdk.Databases(client); + +const result = await databases.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-document.md index 5ec3e0541e..0aaec4e6cb 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-document.md @@ -12,5 +12,6 @@ const result = await databases.upsertDocument({ collectionId: '', documentId: '', data: {}, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-documents.md index 8deec536ff..16ed70fae9 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-nodejs/examples/databases/upsert-documents.md @@ -10,5 +10,6 @@ const databases = new sdk.Databases(client); const result = await databases.upsertDocuments({ databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/messaging/create-push.md b/docs/examples/1.8.x/server-nodejs/examples/messaging/create-push.md index c0a7f4713f..4c64813f25 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-nodejs/examples/messaging/create-push.md @@ -16,7 +16,7 @@ const result = await messaging.createPush({ targets: [], // optional data: {}, // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/server-nodejs/examples/messaging/update-push.md b/docs/examples/1.8.x/server-nodejs/examples/messaging/update-push.md index 5e857f1ff6..6f5899f8c7 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-nodejs/examples/messaging/update-push.md @@ -16,7 +16,7 @@ const result = await messaging.updatePush({ body: '', // optional data: {}, // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..25492396dd --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-operations.md @@ -0,0 +1,23 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-row.md index 29ddab6652..4468c168e8 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-row.md @@ -18,5 +18,6 @@ const result = await tablesDB.createRow({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-rows.md index 61eb1d1db8..20807c1612 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-rows.md @@ -10,5 +10,6 @@ const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.createRows({ databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..249406e333 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.createTransaction({ + ttl: 60 // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/decrement-row-column.md index e3b13a887e..0310399239 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/decrement-row-column.md @@ -13,5 +13,6 @@ const result = await tablesDB.decrementRowColumn({ rowId: '', column: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-row.md index 9802f0d03e..68a965dc97 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-row.md @@ -10,5 +10,6 @@ const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.deleteRow({ databaseId: '', tableId: '', - rowId: '' + rowId: '', + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-rows.md index 6f4d7cd68b..ce1d0f4ba1 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-rows.md @@ -10,5 +10,6 @@ const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.deleteRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..28d086b9cf --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/delete-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.deleteTransaction({ + transactionId: '' +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-row.md index 7ea3d1fca2..fe67e2fda3 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-row.md @@ -11,5 +11,6 @@ const result = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..208368bc00 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/get-transaction.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.getTransaction({ + transactionId: '' +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/increment-row-column.md index f5cac2ebaa..aaa6cd6205 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/increment-row-column.md @@ -13,5 +13,6 @@ const result = await tablesDB.incrementRowColumn({ rowId: '', column: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-rows.md index 6d3b883bd4..3f29781a91 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-rows.md @@ -10,5 +10,6 @@ const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.listRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..3ad0c95425 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/list-transactions.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.listTransactions({ + queries: [] // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-row.md index 1c7f0af197..58583af745 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-row.md @@ -12,5 +12,6 @@ const result = await tablesDB.updateRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-rows.md index 31628d7150..d66fc28a62 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-rows.md @@ -11,5 +11,6 @@ const result = await tablesDB.updateRows({ databaseId: '', tableId: '', data: {}, // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..03501d2cbf --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/update-transaction.md @@ -0,0 +1,14 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-row.md index 9963bb6ced..bfb833356a 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-row.md @@ -12,5 +12,6 @@ const result = await tablesDB.upsertRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-rows.md index cca2b777bb..c985c9515b 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-nodejs/examples/tablesdb/upsert-rows.md @@ -10,5 +10,6 @@ const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.upsertRows({ databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional }); From c903caabcc4553d1dd3ff7f5dbb1369fce4b99a3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 04:50:13 +0000 Subject: [PATCH 156/385] remove dump --- app/controllers/api/account.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 059dce5fc1..e979fc6255 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2958,7 +2958,6 @@ App::patch('/v1/account/password') ->inject('proofForPassword') ->inject('proofForToken') ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { - var_dump('updating password ' . $oldPassword . ':' . $password); $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password From 1c726d046616dd64c17a232cbdaf591fa55c58d4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 04:53:49 +0000 Subject: [PATCH 157/385] fix: encoded not defined --- app/controllers/api/teams.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 4732c6c810..1c96aa0116 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1272,12 +1272,12 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Authorization::setRole(Role::user($userId)->toString()); - if (!Config::getParam('domainVerification')) { - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); + if (!Config::getParam('domainVerification')) { $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); } From 365aaca56df32dc8bbe8c990d432a82175dc5ba5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 04:58:34 +0000 Subject: [PATCH 158/385] fix: remove legacy token generator use --- app/controllers/api/account.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e979fc6255..ed2f839b5b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1720,15 +1720,16 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); + $proofsForTokenOAuth2 = new ProofsToken(TOKEN_LENGTH_OAUTH2); // If the `token` param is set, we will return the token in the query string if ($state['token']) { - $secret = Auth::tokenGenerator(TOKEN_LENGTH_OAUTH2); + $secret = $proofsForTokenOAuth2->generate(); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_OAUTH2, - 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak + 'secret' => $proofsForTokenOAuth2->hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), From 32b290a23150c73933b0e4856cd14cc89da3756e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 10:44:46 +0545 Subject: [PATCH 159/385] Update src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matej Bačo --- .../Platform/Modules/Functions/Http/Executions/Create.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index bed59d96bf..4d14efee3d 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 foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too + if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // Find most recent active session for user ID and JWT headers $current = $session; } } From 3b668f78064f6b9d75fd3cf85b7a35ef5bfa2c40 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 10:45:53 +0545 Subject: [PATCH 160/385] Update composer.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matej Bačo --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a38dc080ed..e456ec1385 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.19.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/auth": "0.4.0", + "utopia-php/auth": "0.4.*", "utopia-php/abuse": "1.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "1.*", From d42841d80aa8eccd14dfde312bd8d00328de6d83 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 05:02:50 +0000 Subject: [PATCH 161/385] fix: remove hardcode, reuse hasher --- app/controllers/api/users.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 95cf41ee09..b8874bf076 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1402,12 +1402,8 @@ App::patch('/v1/users/:userId/password') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', 'argon2') - ->setAttribute('hashOptions', [ - 'memoryCost' => 65536, - 'timeCost' => 4, - 'threads' => 3 - ]); + ->setAttribute('hash', $hasher->getName()) + ->setAttribute('hashOptions', $hasher->getOptions()); $user = $dbForProject->updateDocument('users', $user->getId(), $user); From cfa453079793ac889e87c1ca1cc7dbee42deb513 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 05:03:20 +0000 Subject: [PATCH 162/385] composer update --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 063541a5ba..948b18c211 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": "6e00e41bd4002c54cae87f68e5cb3742", + "content-hash": "883816d2ccfa5372c8c3bb0504dde205", "packages": [ { "name": "adhocore/jwt", From 800db0b99debdbfb4e93e264e3aa8b05147ab4f9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 05:23:30 +0000 Subject: [PATCH 163/385] Fix magic URL token length --- app/controllers/api/account.php | 5 +++-- tests/e2e/Services/Account/AccountCustomClientTest.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ed2f839b5b..3059f3e815 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2034,7 +2034,8 @@ App::post('/v1/account/tokens/magic-url') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $tokenSecret = $proofForToken->generate(); + $proofsForTokenMagicUrl = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); + $tokenSecret = $proofsForTokenMagicUrl->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $token = new Document([ @@ -2042,7 +2043,7 @@ App::post('/v1/account/tokens/magic-url') 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_MAGIC_URL, - 'secret' => $proofForToken->hash($tokenSecret), // One way hash encryption to protect DB leak + 'secret' => $proofsForTokenMagicUrl->hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index a8035ff234..bd3fec8439 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2698,7 +2698,7 @@ class AccountCustomClientTest extends Scope $this->assertStringContainsStringIgnoringCase('Sign in to '. $this->getProject()['name'] . ' with your secure link. Expires in 1 hour.', $lastEmail['text']); $this->assertStringNotContainsStringIgnoringCase('security phrase', $lastEmail['text']); - $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64); $expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0); From 518389d32c854f8bb556e7c716e8d0b2b7b32c6d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 05:37:06 +0000 Subject: [PATCH 164/385] fix length --- app/controllers/api/account.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3059f3e815..38c505f07c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2034,8 +2034,9 @@ App::post('/v1/account/tokens/magic-url') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $proofsForTokenMagicUrl = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); - $tokenSecret = $proofsForTokenMagicUrl->generate(); + $proofForToken->setLength(TOKEN_LENGTH_MAGIC_URL); + + $tokenSecret = $proofForToken->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); $token = new Document([ @@ -2043,7 +2044,7 @@ App::post('/v1/account/tokens/magic-url') 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_MAGIC_URL, - 'secret' => $proofsForTokenMagicUrl->hash($tokenSecret), // One way hash encryption to protect DB leak + 'secret' => $proofForToken->hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), From 13b98409aecfb7d1c0a9ad32c490cab9f2c8bb24 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 05:56:56 +0000 Subject: [PATCH 165/385] reset length --- tests/e2e/Services/Account/AccountCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Account/AccountCustomServerTest.php b/tests/e2e/Services/Account/AccountCustomServerTest.php index e0a52c4007..eb72a99913 100644 --- a/tests/e2e/Services/Account/AccountCustomServerTest.php +++ b/tests/e2e/Services/Account/AccountCustomServerTest.php @@ -218,7 +218,7 @@ class AccountCustomServerTest extends Scope $this->assertEquals($email, $lastEmail['to'][0]['address']); $this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']); - $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64); $expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0); From 19cf94bd7ed1aee53ea1e5a2c586dbc805008a98 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 15 Sep 2025 10:22:45 +0000 Subject: [PATCH 166/385] Fix oauth2 changes --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 38c505f07c..941a28efae 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1501,7 +1501,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $name = ''; $nameOAuth = $oauth2->getUserName($accessToken); - $userParam = \json_decode($request->getParam('user', '{}'), true); // only valid for Apple OAuth2 which returns a user param in the request + $userParam = $request->getParam('user'); if (!empty($nameOAuth)) { $name = $nameOAuth; } elseif ($userParam !== null) { From 4579e41ace3ada4336c3f98f2e811d417ba16658 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 15 Sep 2025 10:33:21 +0000 Subject: [PATCH 167/385] update check --- app/init/resources.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init/resources.php b/app/init/resources.php index a1de0826d5..8e19d26beb 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -265,7 +265,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons } $fallback = $request->getHeader('x-fallback-cookies', ''); $fallback = \json_decode($fallback, true); - $store->decode(((isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); + $store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); } if (APP_MODE_ADMIN !== $mode) { From 780799f87a51160d5d18fa2a9ded1f07aa559683 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 15 Sep 2025 10:41:29 +0000 Subject: [PATCH 168/385] update to initialize first --- src/Appwrite/Platform/Tasks/Install.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index 246c2fb3cc..c70ced33ee 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -158,12 +158,14 @@ class Install extends Action } if ($var['filter'] === 'token') { - $input[$var['name']] = (new Token())->generate(); + $token = new Token(); + $input[$var['name']] = $token->generate(); continue; } if ($var['filter'] === 'password') { - $input[$var['name']] = (new Password())->generate(); + $password = new Password(); + $input[$var['name']] = $password->generate(); continue; } } From b5ca4c116632951f3919a17ab14443431ed1a0c3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 15 Sep 2025 10:43:40 +0000 Subject: [PATCH 169/385] update suggestion --- src/Appwrite/Migration/Version/V17.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index e8d7129f8f..79e2a8377d 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -3,6 +3,7 @@ namespace Appwrite\Migration\Version; use Appwrite\Migration\Migration; +use Utopia\Auth\Proofs\Password; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; @@ -269,7 +270,7 @@ class V17 extends Migration * Set hashOptions type */ $document->setAttribute('hashOptions', array_merge($document->getAttribute('hashOptions', []), [ - 'type' => $document->getAttribute('hash', 'argon2') + 'type' => $document->getAttribute('hash', (new Password())->getHash()->getName()) ])); break; } From cfd82d97095d438d09a336dc4b45b950c6f7121f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 15 Sep 2025 10:57:31 +0000 Subject: [PATCH 170/385] use newer syntax --- app/controllers/general.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 0bd56d6867..599a4af95a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1188,26 +1188,26 @@ App::error() $logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning'; $logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]'; - Console::$logLevel($logPrefix . ' Timestamp: ' . date('c', time())); + Console::{$logLevel}($logPrefix . ' Timestamp: ' . date('c', time())); if ($route) { - Console::$logLevel($logPrefix . ' Status Code: ' . $code); - Console::$logLevel($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath()); + Console::{$logLevel}($logPrefix . ' Status Code: ' . $code); + Console::{$logLevel}($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath()); } - Console::$logLevel($logPrefix . ' Type: ' . get_class($error)); - Console::$logLevel($logPrefix . ' Message: ' . $message); - Console::$logLevel($logPrefix . ' File: ' . $file); - Console::$logLevel($logPrefix . ' Line: ' . $line); - Console::$logLevel($logPrefix . ' Trace:'); + Console::{$logLevel}($logPrefix . ' Type: ' . get_class($error)); + Console::{$logLevel}($logPrefix . ' Message: ' . $message); + Console::{$logLevel}($logPrefix . ' File: ' . $file); + Console::{$logLevel}($logPrefix . ' Line: ' . $line); + Console::{$logLevel}($logPrefix . ' Trace:'); foreach ($trace as $index => $entry) { $traceFile = $entry['file'] ?? 'unknown'; $traceLine = $entry['line'] ?? 0; $traceFunction = $entry['function'] ?? 'unknown'; $traceClass = $entry['class'] ?? ''; $traceType = $entry['type'] ?? ''; - Console::$logLevel(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()"); + Console::{$logLevel}(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()"); } - Console::$logLevel(''); + Console::{$logLevel}(''); } switch ($class) { From 0b9e43c9f82bd84d6390748ec8f55ecdc6791710 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Sep 2025 00:58:23 +0530 Subject: [PATCH 171/385] Branded email for Console auth flows --- app/config/console.php | 40 ++++ .../locale/templates/email-auth-styled.tpl | 224 ++++++++++++++++++ .../locale/templates/email-mfa-challenge.tpl | 2 +- app/config/locale/templates/email-otp.tpl | 2 +- .../locale/templates/email-verification.tpl | 9 + app/controllers/api/account.php | 87 ++++++- 6 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 app/config/locale/templates/email-auth-styled.tpl create mode 100644 app/config/locale/templates/email-verification.tpl diff --git a/app/config/console.php b/app/config/console.php index faacecaa08..164db597e9 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -9,6 +9,8 @@ use Appwrite\Network\Platform; use Utopia\Database\Helpers\ID; use Utopia\System\System; +$localeCodes = include __DIR__ . '/locale/codes.php'; + $console = [ '$id' => ID::custom('console'), '$sequence' => ID::custom('console'), @@ -49,6 +51,44 @@ $console = [ 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') ], + 'templates' => [ + 'email.verification-en' => [ + 'subject' => 'Account Verification', + 'preview' => 'Verify your email to activate your {{project}} account.', + 'heading' => 'Verify your email to start using Appwrite Cloud', + 'hello' => 'Hello {{user}},', + 'body' => 'Thanks for signing up for Appwrite Cloud. Before you can get started, please verify your email address.', + 'footer' => 'If you didn’t create an account, you can ignore this email.', + 'buttonText' => 'Verify email', + 'thanks' => 'Thanks,', + "signature" => "{{project}} team", + ], + 'email.mfaChallenge-en' => [ + 'subject' => 'Verification Code for {{project}}', + 'preview' => 'Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.', + 'heading' => 'Complete two-step verification to use Appwrite Cloud', + 'hello' => 'Hello {{user}},', + 'body' => 'Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.', + 'thanks' => 'Thanks,', + "signature" => "{{project}} team", + ], + 'email.otpSession-en' => [ + 'subject' => 'OTP for {{project}} account login', + 'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.', + 'heading' => 'Login with OTP to use Appwrite Cloud', + 'hello' => 'Hello {{user}},', + 'body' => 'Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.', + 'thanks' => 'Thanks,', + "signature" => "{{project}} team", + ], + ], + 'customEmails' => true, ]; +foreach ($localeCodes as $localeCode) { + $console['templates']['email.verification-'.$localeCode['code']] = $console['templates']['email.verification-en']; + $console['templates']['email.mfaChallenge-'.$localeCode['code']] = $console['templates']['email.mfaChallenge-en']; + $console['templates']['email.otpSession-'.$localeCode['code']] = $console['templates']['email.otpSession-en']; +} + return $console; diff --git a/app/config/locale/templates/email-auth-styled.tpl b/app/config/locale/templates/email-auth-styled.tpl new file mode 100644 index 0000000000..07572c84da --- /dev/null +++ b/app/config/locale/templates/email-auth-styled.tpl @@ -0,0 +1,224 @@ + + + + + + + + + + +

+ {{preview}} +
{{previewWhitespace}}
+
+ +
+ + + + +
+ +
+ + + + + +
+

{{heading}}

+
+ + + + + +
+{{body}} +
+ + + + + +
+ + + + + + + +
+ + + + + +
+ + + + + + +
Terms +
|
+
Privacy
+

+ © {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808, + Delaware, United States +

+
+ + \ No newline at end of file diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl index 3e55227055..fdc0f4d498 100644 --- a/app/config/locale/templates/email-mfa-challenge.tpl +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -5,7 +5,7 @@
-

{{otp}}

+
{{otp}}
diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index 84802c1603..e0d84005d6 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -5,7 +5,7 @@
-

{{otp}}

+
{{otp}}
diff --git a/app/config/locale/templates/email-verification.tpl b/app/config/locale/templates/email-verification.tpl new file mode 100644 index 0000000000..4b68f224db --- /dev/null +++ b/app/config/locale/templates/email-verification.tpl @@ -0,0 +1,9 @@ +

{{hello}}

+

{{body}}

+

{{buttonText}}

+

{{footer}}

+

+ {{thanks}} +
+ {{signature}} +

\ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8aaa5283c4..355ad2ec5d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2295,6 +2295,10 @@ App::post('/v1/account/tokens/email') $preview = $locale->getText("emails.otpSession.preview"); $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; + $customEmails = $project->getAttribute('customEmails', false); + $bodyTemplate = ''; + $heading = ''; + $detector = new Detector($request->getUserAgent('UNKNOWN')); $agentOs = $detector->getOS(); $agentClient = $detector->getClient(); @@ -2360,6 +2364,21 @@ App::post('/v1/account/tokens/email') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); + } else if ($customEmails) { + $subject = $customTemplate['subject']; + $preview = $customTemplate['preview']; + $heading = $customTemplate['heading']; + + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-otp.tpl'); + $message + ->setParam('{{hello}}', $customTemplate['hello']) + ->setParam('{{description}}', $customTemplate['body'], escapeHtml: false) + ->setParam('{{thanks}}', $customTemplate['thanks']) + ->setParam('{{signature}}', $customTemplate['signature']) + ->setParam('{{clientInfo}}', ''); + + $body = $message->render(); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -2374,12 +2393,21 @@ App::post('/v1/account/tokens/email') 'phrase' => !empty($phrase) ? $phrase : '', // TODO: remove unnecessary team variable from this email 'team' => '', + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; $queueForMails ->setSubject($subject) ->setPreview($preview) ->setBody($body) + ->setBodyTemplate($bodyTemplate) ->setVariables($emailVariables) ->setRecipient($email) ->trigger(); @@ -3602,6 +3630,10 @@ App::post('/v1/account/verification') $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); $replyTo = ""; + $customEmails = $project->getAttribute('customEmails', false); + $bodyTemplate = ''; + $heading = ''; + if ($smtpEnabled) { if (!empty($smtp['senderEmail'])) { $senderEmail = $smtp['senderEmail']; @@ -3639,6 +3671,22 @@ App::post('/v1/account/verification') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); + } else if ($customEmails) { + $subject = $customTemplate['subject']; + $preview = $customTemplate['preview']; + $heading = $customTemplate['heading']; + + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-verification.tpl'); + $message + ->setParam('{{hello}}', $customTemplate['hello']) + ->setParam('{{body}}', $customTemplate['body'], escapeHtml: false) + ->setParam('{{buttonText}}', $customTemplate['buttonText']) + ->setParam('{{footer}}', $customTemplate['footer']) + ->setParam('{{thanks}}', $customTemplate['thanks']) + ->setParam('{{signature}}', $customTemplate['signature']); + + $body = $message->render(); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -3649,12 +3697,21 @@ App::post('/v1/account/verification') 'project' => $projectName, // TODO: remove unnecessary team variable from this email 'team' => '', + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; $queueForMails ->setSubject($subject) ->setPreview($preview) ->setBody($body) + ->setBodyTemplate($bodyTemplate) ->setVariables($emailVariables) ->setRecipient($user->getAttribute('email')) ->setName($user->getAttribute('name') ?? '') @@ -4684,6 +4741,10 @@ App::post('/v1/account/mfa/challenge') $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); $replyTo = ""; + $customEmails = $project->getAttribute('customEmails', false); + $bodyTemplate = ''; + $heading = ''; + if ($smtpEnabled) { if (!empty($smtp['senderEmail'])) { $senderEmail = $smtp['senderEmail']; @@ -4721,6 +4782,21 @@ App::post('/v1/account/mfa/challenge') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); + } else if ($customEmails) { + $subject = $customTemplate['subject']; + $preview = $customTemplate['preview']; + $heading = $customTemplate['heading']; + + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl'); + $message + ->setParam('{{hello}}', $customTemplate['hello']) + ->setParam('{{description}}', $customTemplate['body'], escapeHtml: false) + ->setParam('{{thanks}}', $customTemplate['thanks']) + ->setParam('{{signature}}', $customTemplate['signature']) + ->setParam('{{clientInfo}}', ''); + + $body = $message->render(); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -4731,13 +4807,22 @@ App::post('/v1/account/mfa/challenge') 'otp' => $code, 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', - 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN' + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; $queueForMails ->setSubject($subject) ->setPreview($preview) ->setBody($body) + ->setBodyTemplate($bodyTemplate) ->setVariables($emailVariables) ->setRecipient($user->getAttribute('email')) ->trigger(); From 42d981c0ef813d213f1fec5021c4fd6cc66f5e57 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 16 Sep 2025 13:15:08 +1200 Subject: [PATCH 172/385] Add scopes to roles --- app/config/roles.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/config/roles.php b/app/config/roles.php index 0f0945a2b4..6a4bba2699 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -16,6 +16,8 @@ $member = [ 'documents.write', 'rows.read', 'rows.write', + 'transactions.read', + 'transactions.write', 'files.read', 'files.write', 'projects.read', @@ -41,6 +43,8 @@ $admins = [ 'documents.write', 'rows.read', 'rows.write', + 'transactions.read', + 'transactions.write', 'files.read', 'files.write', 'buckets.read', From fb65d7a965d0dc263fa7c28f8709bb9f6b0522ca Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 16 Sep 2025 13:25:15 +1200 Subject: [PATCH 173/385] Add scoped tests --- .../{TransactionsTest.php => TransactionsBase.php} | 8 +------- .../Transactions/TransactionsConsoleClientTest.php | 13 +++++++++++++ .../Transactions/TransactionsCustomClientTest.php | 13 +++++++++++++ .../Transactions/TransactionsCustomServerTest.php | 13 +++++++++++++ .../{TransactionsTest.php => TransactionsBase.php} | 8 +------- .../Transactions/TransactionsConsoleClientTest.php | 13 +++++++++++++ .../Transactions/TransactionsCustomClientTest.php | 13 +++++++++++++ .../Transactions/TransactionsCustomServerTest.php | 13 +++++++++++++ 8 files changed, 80 insertions(+), 14 deletions(-) rename tests/e2e/Services/Databases/Legacy/Transactions/{TransactionsTest.php => TransactionsBase.php} (99%) create mode 100644 tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php create mode 100644 tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php create mode 100644 tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php rename tests/e2e/Services/Databases/TablesDB/Transactions/{TransactionsTest.php => TransactionsBase.php} (99%) create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php similarity index 99% rename from tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php rename to tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php index a76d909b5e..f05563123c 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php @@ -3,19 +3,13 @@ namespace Tests\E2E\Services\Databases\Legacy\Transactions; use Tests\E2E\Client; -use Tests\E2E\Scopes\ProjectCustom; -use Tests\E2E\Scopes\Scope; -use Tests\E2E\Scopes\SideClient; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; -class TransactionsTest extends Scope +trait TransactionsBase { - use ProjectCustom; - use SideClient; - /** * Test creating a transaction */ diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php new file mode 100644 index 0000000000..427e79fb3a --- /dev/null +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php @@ -0,0 +1,13 @@ + Date: Tue, 16 Sep 2025 04:27:00 +0000 Subject: [PATCH 174/385] Fix re-hashing --- app/controllers/api/account.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 941a28efae..665d32ba11 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -961,10 +961,11 @@ App::post('/v1/account/sessions/email') // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) { + $proofForPasswordUpdated = new ProofsPassword(); $user - ->setAttribute('password', (new ProofsPassword())->hash($password)) - ->setAttribute('hash', $proofForPassword->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()); + ->setAttribute('password', $proofForPasswordUpdated->hash($password)) + ->setAttribute('hash', $proofForPasswordUpdated->getHash()->getName()) + ->setAttribute('hashOptions', $proofForPasswordUpdated->getHash()->getOptions()); $dbForProject->updateDocument('users', $user->getId(), $user); } From 73e7c98131dcbf56442cb0c69b128561f2809bea Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 16 Sep 2025 04:36:30 +0000 Subject: [PATCH 175/385] Fix token length update --- app/controllers/api/account.php | 5 ++--- app/controllers/api/users.php | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 665d32ba11..a2c031451a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1969,8 +1969,7 @@ App::post('/v1/account/tokens/magic-url') ->inject('queueForEvents') ->inject('queueForMails') ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2035,7 +2034,7 @@ App::post('/v1/account/tokens/magic-url') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $proofForToken->setLength(TOKEN_LENGTH_MAGIC_URL); + $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); $tokenSecret = $proofForToken->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index b8874bf076..8b4967144d 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2307,17 +2307,15 @@ App::post('/v1/users/:userId/tokens') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('proofForToken') - ->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Token $proofForToken) { + ->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } - $secret = $proofForToken - ->setLength($length) - ->generate(); + $proofForToken = new Token($length); + $secret = $proofForToken->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire)); $token = new Document([ From 33f7056e7a5f057f24e73fbca80cb70f179e9ff5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 16 Sep 2025 04:38:52 +0000 Subject: [PATCH 176/385] reest callback --- app/controllers/api/account.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a2c031451a..cfe31d0b04 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1401,8 +1401,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('proofForPassword') ->inject('proofForToken') ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) { - $protocol = $request->getProtocol(); - $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; + $port = $request->getPort(); + $callbackBase = $protocol . '://' . $request->getHostname(); + if ($protocol === 'https' && $port !== '443') { + $callbackBase .= ':' . $port; + } elseif ($protocol === 'http' && $port !== '80') { + $callbackBase .= ':' . $port; + } + $callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $redirect = new Redirect($platforms); $appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? ''; From f8ab95b3e15636fa25cb8f346d516d2b5b43ec89 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Sep 2025 11:54:01 +0530 Subject: [PATCH 177/385] tests & lint --- app/config/console.php | 12 +----------- app/controllers/api/account.php | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/config/console.php b/app/config/console.php index 164db597e9..9690cb8f28 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -71,16 +71,7 @@ $console = [ 'body' => 'Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.', 'thanks' => 'Thanks,', "signature" => "{{project}} team", - ], - 'email.otpSession-en' => [ - 'subject' => 'OTP for {{project}} account login', - 'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.', - 'heading' => 'Login with OTP to use Appwrite Cloud', - 'hello' => 'Hello {{user}},', - 'body' => 'Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.', - 'thanks' => 'Thanks,', - "signature" => "{{project}} team", - ], + ] ], 'customEmails' => true, ]; @@ -88,7 +79,6 @@ $console = [ foreach ($localeCodes as $localeCode) { $console['templates']['email.verification-'.$localeCode['code']] = $console['templates']['email.verification-en']; $console['templates']['email.mfaChallenge-'.$localeCode['code']] = $console['templates']['email.mfaChallenge-en']; - $console['templates']['email.otpSession-'.$localeCode['code']] = $console['templates']['email.otpSession-en']; } return $console; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 355ad2ec5d..37ebceba17 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2364,7 +2364,7 @@ App::post('/v1/account/tokens/email') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } else if ($customEmails) { + } elseif ($customEmails && !empty($customTemplate)) { $subject = $customTemplate['subject']; $preview = $customTemplate['preview']; $heading = $customTemplate['heading']; @@ -3671,7 +3671,7 @@ App::post('/v1/account/verification') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } else if ($customEmails) { + } elseif ($customEmails && !empty($customTemplate)) { $subject = $customTemplate['subject']; $preview = $customTemplate['preview']; $heading = $customTemplate['heading']; @@ -4782,7 +4782,7 @@ App::post('/v1/account/mfa/challenge') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } else if ($customEmails) { + } elseif ($customEmails && !empty($customTemplate)) { $subject = $customTemplate['subject']; $preview = $customTemplate['preview']; $heading = $customTemplate['heading']; From e6be9bac0774e38c4ddd9ef5660aa4e4932a0370 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Sep 2025 11:55:43 +0530 Subject: [PATCH 178/385] cleanup --- app/config/locale/templates/email-auth-styled.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/locale/templates/email-auth-styled.tpl b/app/config/locale/templates/email-auth-styled.tpl index 07572c84da..544e58b7d8 100644 --- a/app/config/locale/templates/email-auth-styled.tpl +++ b/app/config/locale/templates/email-auth-styled.tpl @@ -38,7 +38,7 @@ background-color: #ffffff; margin: 0; padding: 0; - } 1Code has comments. Press enter to view. + } a { color: currentColor; word-break: break-all; From 614285f1ae1137ad086c171b8a709b30c06f6663 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Sep 2025 11:29:13 +0300 Subject: [PATCH 179/385] Add Scope for tests --- .../TablesDB/Transactions/TransactionsConsoleClientTest.php | 3 ++- .../TablesDB/Transactions/TransactionsCustomClientTest.php | 3 ++- .../TablesDB/Transactions/TransactionsCustomServerTest.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php index 553c620f20..2159390fa2 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\TablesDB\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideConsole; -class TransactionsConsoleClientTest +class TransactionsConsoleClientTest extends Scope { use TransactionsBase; use ProjectCustom; diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php index 3b7355faaf..732537fc7b 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\TablesDB\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -class TransactionsCustomClientTest +class TransactionsCustomClientTest extends Scope { use TransactionsBase; use ProjectCustom; diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php index 01d4a8d057..57588788b1 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\TablesDB\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; -class TransactionsCustomServerTest +class TransactionsCustomServerTest extends Scope { use TransactionsBase; use ProjectCustom; From 21f25b4ce4241923d52ffcefac3fc487dded330b Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Sep 2025 14:16:02 +0530 Subject: [PATCH 180/385] Auto-allow sites domain for OAuth --- app/init/resources.php | 63 ++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index e4e8fbef5e..99b6e74382 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -151,7 +151,7 @@ App::setResource('queueForMigrations', function (Publisher $publisher) { App::setResource('queueForStatsResources', function (Publisher $publisher) { return new StatsResources($publisher); }, ['publisher']); -App::setResource('platforms', function (Request $request, Document $console, Document $project) { +App::setResource('platforms', function (Request $request, Document $console, Document $project, Database $dbForPlatform) { $console->setAttribute('platforms', [ // Always allow current host '$collection' => ID::custom('platforms'), 'name' => 'Current Host', @@ -190,11 +190,52 @@ App::setResource('platforms', function (Request $request, Document $console, Doc ], Document::SET_TYPE_APPEND); } + $origin = \parse_url($request->getOrigin(), PHP_URL_HOST); + + if (empty($origin)) { + $origin = \parse_url($request->getReferer(), PHP_URL_HOST); + } + + // Safe if rule with same project ID exists + if (!empty($origin)) { + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? ''))); + } else { + $rule = Authorization::skip( + fn () => $dbForPlatform->find('rules', [ + Query::equal('domain', [$origin]), + Query::limit(1) + ]) + )[0] ?? new Document(); + } + + var_dump($rule); + + if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { + echo "inside project internal id\n"; + + $project->setAttribute('platforms', [ + '$collection' => ID::custom('platforms'), + 'type' => Platform::TYPE_WEB, + 'name' => $origin, + 'hostname' => $origin, + ], Document::SET_TYPE_APPEND); + } + } + + // Unsafe; Localhost is always safe for ease of local development + $project->setAttribute('platforms', [ + '$collection' => ID::custom('platforms'), + 'type' => Platform::TYPE_WEB, + 'name' => "localhost", + 'hostname' => "localhost", + ], Document::SET_TYPE_APPEND); + return [ ...$console->getAttribute('platforms', []), ...$project->getAttribute('platforms', []), ]; -}, ['request', 'console', 'project']); +}, ['request', 'console', 'project', 'dbForPlatform']); App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) { /** @var Appwrite\Utopia\Request $request */ @@ -1005,24 +1046,6 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef return $referrer; } - // Safe if rule with same project ID exists - if (!empty($origin)) { - if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { - $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? ''))); - } else { - $rule = Authorization::skip( - fn () => $dbForPlatform->find('rules', [ - Query::equal('domain', [$origin]), - Query::limit(1) - ]) - )[0] ?? new Document(); - } - - if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { - return $referrer; - } - } - // Unsafe; Localhost is always safe for ease of local development $origin = 'localhost'; $protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME); From b38b2bba0c81d184d67da5cf5ad78ea74494eb14 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 16 Sep 2025 21:13:41 +1200 Subject: [PATCH 181/385] Remove state order on created, rely on sequence order --- src/Appwrite/Databases/TransactionState.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 7d99d61d01..1caae8c629 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -31,7 +31,6 @@ class TransactionState // Fetch operations ordered by sequence to replay in exact order $operations = $this->dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc('$createdAt'), // Ensure operations are processed in order ]); $state = []; From 74f181d7a834c4874f231c4c5dd727ae25ef1c8a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 17 Sep 2025 10:18:49 +0000 Subject: [PATCH 182/385] fix token length --- app/controllers/api/account.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index cfe31d0b04..64e90e9b03 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2488,8 +2488,7 @@ App::put('/v1/account/sessions/magic-url') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->inject('proofForToken') - ->action($createSession); + ->action(fn ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store) => $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, new ProofsToken(TOKEN_LENGTH_MAGIC_URL))); App::put('/v1/account/sessions/phone') ->desc('Update phone session') From 3065f53d83bdfc0a926027cce01955661ac6f610 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 18 Sep 2025 01:14:14 +0000 Subject: [PATCH 183/385] Fix token hash --- app/controllers/api/account.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 64e90e9b03..e354a19b9a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -40,6 +40,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; +use Utopia\Auth\Hashes\Sha; use Utopia\Auth\Proofs\Code as ProofsCode; use Utopia\Auth\Proofs\Password as ProofsPassword; use Utopia\Auth\Proofs\Token as ProofsToken; @@ -2042,6 +2043,7 @@ App::post('/v1/account/tokens/magic-url') } $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); + $proofForToken->setHash(new Sha()); $tokenSecret = $proofForToken->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); From 4540362f42da32d29df59cb3498cd2b6aa7c75bd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 18 Sep 2025 01:37:19 +0000 Subject: [PATCH 184/385] Fix: token hash magic url session --- app/controllers/api/account.php | 6 +++++- app/controllers/api/users.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e354a19b9a..18e2aed277 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2490,7 +2490,11 @@ App::put('/v1/account/sessions/magic-url') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->action(fn ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store) => $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, new ProofsToken(TOKEN_LENGTH_MAGIC_URL))); + ->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store) use ($createSession) { + $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); + $proofForToken->setHash(new Sha()); + $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken); + }); App::put('/v1/account/sessions/phone') ->desc('Update phone session') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 8b4967144d..536adcf128 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2315,6 +2315,7 @@ App::post('/v1/users/:userId/tokens') } $proofForToken = new Token($length); + $proofForToken->setHash(new Sha()); $secret = $proofForToken->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire)); From 2021396f39660bc915c7b873771e20c7c28cfa05 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 18 Sep 2025 12:33:42 +0530 Subject: [PATCH 185/385] email otp branded --- app/config/console.php | 16 ++++-- app/config/locale/templates/email-otp.tpl | 4 +- app/controllers/api/account.php | 59 +++++++++++++---------- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/app/config/console.php b/app/config/console.php index 9690cb8f28..ae6c15fd64 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -71,14 +71,24 @@ $console = [ 'body' => 'Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.', 'thanks' => 'Thanks,', "signature" => "{{project}} team", - ] + ], + 'email.otpSession-en' => [ + 'subject' => 'OTP for {{project}} account login', + 'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.', + 'heading' => 'Login with OTP to use Appwrite Cloud', + 'hello' => 'Hello {{user}},', + 'body' => 'Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.', + 'thanks' => 'Thanks,', + "signature" => "{{project}} team", + ], ], 'customEmails' => true, ]; foreach ($localeCodes as $localeCode) { - $console['templates']['email.verification-'.$localeCode['code']] = $console['templates']['email.verification-en']; - $console['templates']['email.mfaChallenge-'.$localeCode['code']] = $console['templates']['email.mfaChallenge-en']; + $console['templates']['email.verification-' . $localeCode['code']] = $console['templates']['email.verification-en']; + $console['templates']['email.mfaChallenge-' . $localeCode['code']] = $console['templates']['email.mfaChallenge-en']; + $console['templates']['email.otpSession-' . $localeCode['code']] = $console['templates']['email.otpSession-en']; } return $console; diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index e0d84005d6..aebc512e1b 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -5,7 +5,7 @@
-
{{otp}}
+
{{otp}}
@@ -15,6 +15,6 @@

{{thanks}}

{{signature}}

-
+

{{securityPhrase}}

\ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 37ebceba17..bde3672774 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -165,7 +165,8 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc ->setVariables($emailVariables) ->setRecipient($email) ->trigger(); -}; +} +; $createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { @@ -838,7 +839,7 @@ App::patch('/v1/account/sessions/:sessionId') $session ->setAttribute('providerAccessToken', $oauth2->getAccessToken('')) ->setAttribute('providerRefreshToken', $oauth2->getRefreshToken('')) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); + ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $oauth2->getAccessTokenExpiry(''))); } // Save changes @@ -982,9 +983,11 @@ App::post('/v1/account/sessions/email') ; if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) { - if ($dbForProject->count('sessions', [ - Query::equal('userId', [$user->getId()]), - ]) !== 1) { + if ( + $dbForProject->count('sessions', [ + Query::equal('userId', [$user->getId()]), + ]) !== 1 + ) { sendSessionAlert($locale, $user, $project, $session, $queueForMails); } } @@ -1098,7 +1101,7 @@ App::post('/v1/account/sessions/anonymous') Authorization::setRole(Role::user($user->getId())->toString()); - $session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [ + $session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), Permission::delete(Role::user($user->getId())), @@ -1659,13 +1662,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerEmail' => $email, 'providerAccessToken' => $accessToken, 'providerRefreshToken' => $refreshToken, - 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry), + 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry), ])); } else { $identity ->setAttribute('providerAccessToken', $accessToken) ->setAttribute('providerRefreshToken', $refreshToken) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry)); + ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry)); $dbForProject->updateDocument('identities', $identity->getId(), $identity); } @@ -1735,7 +1738,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerUid' => $oauth2ID, 'providerAccessToken' => $accessToken, 'providerRefreshToken' => $refreshToken, - 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry), + 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry), 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2314,8 +2317,10 @@ App::post('/v1/account/tokens/email') if (!empty($phrase)) { $message->setParam('{{securityPhrase}}', $locale->getText("emails.otpSession.securityPhrase")); + $message->setParam('{{securityPhraseDividerDisplay}}', 'block'); } else { $message->setParam('{{securityPhrase}}', ''); + $message->setParam('{{securityPhraseDividerDisplay}}', 'none'); } $body = $message->render(); @@ -2375,7 +2380,9 @@ App::post('/v1/account/tokens/email') ->setParam('{{description}}', $customTemplate['body'], escapeHtml: false) ->setParam('{{thanks}}', $customTemplate['thanks']) ->setParam('{{signature}}', $customTemplate['signature']) - ->setParam('{{clientInfo}}', ''); + ->setParam('{{clientInfo}}', '') + ->setParam('{{securityPhrase}}', '') + ->setParam('{{securityPhraseDividerDisplay}}', 'none'); $body = $message->render(); $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; @@ -2760,10 +2767,12 @@ App::post('/v1/account/jwts') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic(new Document(['jwt' => $jwt->encode([ - 'userId' => $user->getId(), - 'sessionId' => $current->getId(), - ])]), Response::MODEL_JWT); + ->dynamic(new Document([ + 'jwt' => $jwt->encode([ + 'userId' => $user->getId(), + 'sessionId' => $current->getId(), + ]) + ]), Response::MODEL_JWT); }); App::get('/v1/account/prefs') @@ -3508,12 +3517,12 @@ App::put('/v1/account/recovery') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile - ->setAttribute('password', $newPassword) - ->setAttribute('passwordHistory', $history) - ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('emailVerification', true)); + ->setAttribute('password', $newPassword) + ->setAttribute('passwordHistory', $history) + ->setAttribute('passwordUpdate', DateTime::now()) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('emailVerification', true)); $user->setAttributes($profile->getArrayCopy()); @@ -4945,8 +4954,8 @@ App::put('/v1/account/mfa/challenge') $dbForProject->updateDocument('sessions', $session->getId(), $session); $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()); + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()); $response->dynamic($session, Response::MODEL_SESSION); }); @@ -5009,7 +5018,7 @@ App::post('/v1/account/targets/push') ], 'providerId' => !empty($providerId) ? $providerId : null, 'providerInternalId' => !empty($providerId) ? $provider->getSequence() : null, - 'providerType' => MESSAGE_TYPE_PUSH, + 'providerType' => MESSAGE_TYPE_PUSH, 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'sessionId' => $session->getId(), @@ -5184,8 +5193,8 @@ App::get('/v1/account/identities') $queries[] = Query::equal('userInternalId', [$user->getSequence()]); /** - * Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries - */ + * Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries + */ $cursor = \array_filter($queries, function ($query) { return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); From dedc7a35c96f3e4a7bc89ff363fabf2d35e5be86 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 18 Sep 2025 12:38:18 +0530 Subject: [PATCH 186/385] remove debug output --- app/init/resources.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 99b6e74382..4b5452eb46 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -212,8 +212,6 @@ App::setResource('platforms', function (Request $request, Document $console, Doc var_dump($rule); if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { - echo "inside project internal id\n"; - $project->setAttribute('platforms', [ '$collection' => ID::custom('platforms'), 'type' => Platform::TYPE_WEB, @@ -416,7 +414,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) - ->setTenant((int)$project->getSequence()) + ->setTenant((int) $project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { $database @@ -469,7 +467,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) - ->setTenant((int)$project->getSequence()) + ->setTenant((int) $project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { $database @@ -499,7 +497,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) { return function (?Document $project = null) use ($pools, $cache, &$database) { if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant((int)$project->getSequence()); + $database->setTenant((int) $project->getSequence()); return $database; } @@ -514,7 +512,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) { // set tenant if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant((int)$project->getSequence()); + $database->setTenant((int) $project->getSequence()); } return $database; @@ -542,7 +540,7 @@ App::setResource('redis', function () { $pass = System::getEnv('_APP_REDIS_PASS', ''); $redis = new \Redis(); - @$redis->pconnect($host, (int)$port); + @$redis->pconnect($host, (int) $port); if ($pass) { $redis->auth($pass); } @@ -755,7 +753,7 @@ App::setResource('schema', function ($utopia, $dbForProject) { // NOTE: `params` and `urls` are not used internally in the `Schema::build` function below! $params = [ 'list' => function (string $databaseId, string $collectionId, array $args) { - return [ 'queries' => $args['queries']]; + return ['queries' => $args['queries']]; }, 'create' => function (string $databaseId, string $collectionId, array $args) { $id = $args['id'] ?? 'unique()'; @@ -1004,7 +1002,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) { } $accessedAt = $token->getAttribute('accessedAt', 0); - if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), - APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) { + if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) { $token->setAttribute('accessedAt', DatabaseDateTime::now()); Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token)); } From 0812ac97a997012dfa3eaa817bd2f04027ade901 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 18 Sep 2025 14:09:42 +0530 Subject: [PATCH 187/385] tests --- app/config/console.php | 2 +- app/config/locale/templates/email-auth-styled.tpl | 1 + tests/e2e/Services/Account/AccountBase.php | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/config/console.php b/app/config/console.php index ae6c15fd64..6f44368060 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -73,7 +73,7 @@ $console = [ "signature" => "{{project}} team", ], 'email.otpSession-en' => [ - 'subject' => 'OTP for {{project}} account login', + 'subject' => 'OTP for {{project}} Login', 'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.', 'heading' => 'Login with OTP to use Appwrite Cloud', 'hello' => 'Hello {{user}},', diff --git a/app/config/locale/templates/email-auth-styled.tpl b/app/config/locale/templates/email-auth-styled.tpl index 544e58b7d8..a826c62e95 100644 --- a/app/config/locale/templates/email-auth-styled.tpl +++ b/app/config/locale/templates/email-auth-styled.tpl @@ -144,6 +144,7 @@ Appwrite logo diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 8813e2784f..6cf997e22c 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -152,6 +152,8 @@ trait AccountBase public function testEmailOTPSession(): void { + $isConsoleProject = $this->getProject()['$id'] === 'console'; + $response = $this->client->call(Client::METHOD_POST, '/account/tokens/email', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -183,6 +185,16 @@ trait AccountBase $this->assertNotEmpty($code); $this->assertStringContainsStringIgnoringCase('Use OTP ' . $code . ' to sign in to '. $this->getProject()['name'] . '. Expires in 15 minutes.', $lastEmail['text']); + // Only Console project has branded logo in email. + if ($isConsoleProject) { + $this->assertStringContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']); + } + + // TODO: Remove this once OTP login is supported for Console. + if ($isConsoleProject) { + return; + } + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', From 5fd2dfafb72ab4500cc4e2d3b0279a3bb21583af Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 18 Sep 2025 14:10:56 +0530 Subject: [PATCH 188/385] remove var_dump --- app/init/resources.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 4b5452eb46..211bc52a8e 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -209,8 +209,6 @@ App::setResource('platforms', function (Request $request, Document $console, Doc )[0] ?? new Document(); } - var_dump($rule); - if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { $project->setAttribute('platforms', [ '$collection' => ID::custom('platforms'), From 4b90998d1e0844b3b1de078f113e1adafbfe34b6 Mon Sep 17 00:00:00 2001 From: Veeresh <75656445+Veera-mulge@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:04:27 +0530 Subject: [PATCH 189/385] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89390515eb..4cba193d4d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> We just announced inversion queries for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-inversion-queries) +> We just announced API for spatial columns for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-spatial-columns) > Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga) From 3e8211d6bd199cd731b0a637b6b5a9fe9beb000b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 20 Sep 2025 19:45:44 +0530 Subject: [PATCH 190/385] refactor: improve code readability for schedules --- app/controllers/api/messaging.php | 13 +++++++------ src/Appwrite/Migration/Version/V19.php | 5 +++-- .../Modules/Functions/Http/Functions/Create.php | 3 ++- src/Appwrite/Platform/Tasks/ScheduleExecutions.php | 6 ++++++ src/Appwrite/Platform/Tasks/ScheduleFunctions.php | 7 ++++++- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index d22c5cb2c2..bde93305dd 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -10,6 +10,7 @@ use Appwrite\Extend\Exception; use Appwrite\Messaging\Status as MessageStatus; use Appwrite\Network\Validator\Email; use Appwrite\Permission; +use Appwrite\Platform\Tasks\ScheduleMessages; use Appwrite\Role; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; @@ -3102,7 +3103,7 @@ App::post('/v1/messaging/messages/email') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3244,7 +3245,7 @@ App::post('/v1/messaging/messages/sms') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3462,7 +3463,7 @@ App::post('/v1/messaging/messages/push') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3863,7 +3864,7 @@ App::patch('/v1/messaging/messages/email/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4084,7 +4085,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4258,7 +4259,7 @@ App::patch('/v1/messaging/messages/push/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'message', + 'resourceType' => ScheduleMessages::getSupportedResource(), 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index d4dda02d75..790a2fdfb7 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -3,6 +3,7 @@ namespace Appwrite\Migration\Version; use Appwrite\Migration\Migration; +use Appwrite\Platform\Tasks\ScheduleFunctions; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -730,8 +731,8 @@ class V19 extends Migration if (empty($document->getAttribute('scheduleId', null))) { $schedule = $this->dbForPlatform->createDocument('schedules', new Document([ - 'region' => $project->getAttribute('region'), - 'resourceType' => 'function', + 'region' => $this->project->getAttribute('region'), + 'resourceType' => ScheduleFunctions::getSupportedResource(), 'resourceId' => $document->getId(), 'resourceInternalId' => $document->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 21a74f9a81..ccf5abc373 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -11,6 +11,7 @@ use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Compute\Base; use Appwrite\Platform\Modules\Compute\Validator\Specification; +use Appwrite\Platform\Tasks\ScheduleFunctions; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -235,7 +236,7 @@ class Create extends Base $schedule = Authorization::skip( fn () => $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => 'function', + 'resourceType' => ScheduleFunctions::getSupportedResource(), 'resourceId' => $function->getId(), 'resourceInternalId' => $function->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 14a4259e17..438341ad47 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -6,6 +6,12 @@ use Appwrite\Event\Func; use Swoole\Coroutine as Co; use Utopia\Database\Database; +/** + * ScheduleExecutions + * + * Handles delayed executions by processing one-time scheduled tasks + * that are executed at a specific future time. + */ class ScheduleExecutions extends ScheduleBase { public const UPDATE_TIMER = 3; // seconds diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 6f072425e4..090adcbccf 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -7,8 +7,13 @@ use Cron\CronExpression; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Pools\Group; +/** + * ScheduleFunctions + * + * Handles cron job related executions by processing cron expressions + * and scheduling function executions based on recurring schedules. + */ class ScheduleFunctions extends ScheduleBase { public const UPDATE_TIMER = 10; // seconds From 2f1046db2b1f63b1fd7bd7989d1b20ec74e86d10 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 22 Sep 2025 12:10:55 +0530 Subject: [PATCH 191/385] fix tests --- app/init/resources.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 211bc52a8e..16307edee3 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -219,14 +219,6 @@ App::setResource('platforms', function (Request $request, Document $console, Doc } } - // Unsafe; Localhost is always safe for ease of local development - $project->setAttribute('platforms', [ - '$collection' => ID::custom('platforms'), - 'type' => Platform::TYPE_WEB, - 'name' => "localhost", - 'hostname' => "localhost", - ], Document::SET_TYPE_APPEND); - return [ ...$console->getAttribute('platforms', []), ...$project->getAttribute('platforms', []), From f09d5258ece14f4b3c9cf3ab9d0c82947269b47d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 22 Sep 2025 14:20:58 +0530 Subject: [PATCH 192/385] use constants --- app/init/constants.php | 10 ++++++++++ src/Appwrite/Platform/Tasks/ScheduleExecutions.php | 4 ++-- src/Appwrite/Platform/Tasks/ScheduleFunctions.php | 4 ++-- src/Appwrite/Platform/Tasks/ScheduleMessages.php | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index 28cf8a4052..2c94041276 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -279,3 +279,13 @@ const TOKENS_RESOURCE_TYPE_FILES = 'files'; const TOKENS_RESOURCE_TYPE_SITES = 'sites'; const TOKENS_RESOURCE_TYPE_FUNCTIONS = 'functions'; const TOKENS_RESOURCE_TYPE_DATABASES = 'databases'; + +// schedules types +const SCHEDULE_TYPE_EXECUTION = 'execution'; +const SCHEDULE_TYPE_FUNCTION = 'function'; +const SCHEDULE_TYPE_MESSAGE = 'message'; + +// collections types +const COLLECTION_TYPE_EXECUTIONS = 'executions'; +const COLLECTION_TYPE_FUNCTIONS = 'functions'; +const COLLECTION_TYPE_MESSAGES = 'messages'; diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 438341ad47..77cc056280 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -24,12 +24,12 @@ class ScheduleExecutions extends ScheduleBase public static function getSupportedResource(): string { - return 'execution'; + return SCHEDULE_TYPE_EXECUTION; } public static function getCollectionId(): string { - return 'executions'; + return COLLECTION_TYPE_EXECUTIONS; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 090adcbccf..09c7296f93 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -28,12 +28,12 @@ class ScheduleFunctions extends ScheduleBase public static function getSupportedResource(): string { - return 'function'; + return SCHEDULE_TYPE_FUNCTION; } public static function getCollectionId(): string { - return 'functions'; + return COLLECTION_TYPE_FUNCTIONS; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index fe4afbe69c..87e5ba5730 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -17,12 +17,12 @@ class ScheduleMessages extends ScheduleBase public static function getSupportedResource(): string { - return 'message'; + return SCHEDULE_TYPE_MESSAGE; } public static function getCollectionId(): string { - return 'messages'; + return COLLECTION_TYPE_MESSAGES; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void From 0228c74f71569c9f6013116490b00a5bf9b30563 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 22 Sep 2025 14:26:23 +0530 Subject: [PATCH 193/385] Throw error when email is not available for account verification --- app/controllers/api/account.php | 4 ++++ .../Account/AccountCustomClientTest.php | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8aaa5283c4..09f5036188 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3544,6 +3544,10 @@ App::post('/v1/account/verification') throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } + if (empty($user->getAttribute('email'))) { + throw new Exception(Exception::USER_EMAIL_NOT_FOUND); + } + $url = htmlentities($url); if ($user->getAttribute('emailVerification')) { throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index bd3fec8439..5cec3770f7 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1850,6 +1850,26 @@ class AccountCustomClientTest extends Scope return $session; } + /** + * @depends testCreateAnonymousAccount + */ + public function testCreateAnonymousAccountVerification($session): array + { + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $this->assertEquals(500, $response['body']['code']); + $this->assertEquals('user_email_not_found', $response['body']['type']); + + return []; + } + /** * @depends testCreateAnonymousAccount */ From c52081dbed972c20686f343f7eb96d9d545dbb94 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 22 Sep 2025 19:58:18 +0530 Subject: [PATCH 194/385] fix: to use correct pattern --- app/init/constants.php | 15 ++++----------- .../Platform/Tasks/ScheduleExecutions.php | 4 ++-- src/Appwrite/Platform/Tasks/ScheduleFunctions.php | 4 ++-- src/Appwrite/Platform/Tasks/ScheduleMessages.php | 4 ++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index 2c94041276..5c75dc4ce2 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -262,7 +262,6 @@ const METRIC_SITES_ID_INBOUND = 'sites.{siteInternalId}.inbound'; const METRIC_SITES_ID_OUTBOUND = 'sites.{siteInternalId}.outbound'; // Resource types - const RESOURCE_TYPE_PROJECTS = 'projects'; const RESOURCE_TYPE_FUNCTIONS = 'functions'; const RESOURCE_TYPE_SITES = 'sites'; @@ -274,18 +273,12 @@ const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers'; const RESOURCE_TYPE_MESSAGES = 'messages'; // Resource types for Tokens - const TOKENS_RESOURCE_TYPE_FILES = 'files'; const TOKENS_RESOURCE_TYPE_SITES = 'sites'; const TOKENS_RESOURCE_TYPE_FUNCTIONS = 'functions'; const TOKENS_RESOURCE_TYPE_DATABASES = 'databases'; -// schedules types -const SCHEDULE_TYPE_EXECUTION = 'execution'; -const SCHEDULE_TYPE_FUNCTION = 'function'; -const SCHEDULE_TYPE_MESSAGE = 'message'; - -// collections types -const COLLECTION_TYPE_EXECUTIONS = 'executions'; -const COLLECTION_TYPE_FUNCTIONS = 'functions'; -const COLLECTION_TYPE_MESSAGES = 'messages'; +// Resource types for Schedules +const SCHEDULE_RESOURCE_TYPE_EXECUTION = 'execution'; +const SCHEDULE_RESOURCE_TYPE_FUNCTION = 'function'; +const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message'; diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 77cc056280..83a3f51b03 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -24,12 +24,12 @@ class ScheduleExecutions extends ScheduleBase public static function getSupportedResource(): string { - return SCHEDULE_TYPE_EXECUTION; + return SCHEDULE_RESOURCE_TYPE_EXECUTION; } public static function getCollectionId(): string { - return COLLECTION_TYPE_EXECUTIONS; + return RESOURCE_TYPE_EXECUTIONS; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 09c7296f93..7fda2f75df 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -28,12 +28,12 @@ class ScheduleFunctions extends ScheduleBase public static function getSupportedResource(): string { - return SCHEDULE_TYPE_FUNCTION; + return SCHEDULE_RESOURCE_TYPE_FUNCTION; } public static function getCollectionId(): string { - return COLLECTION_TYPE_FUNCTIONS; + return RESOURCE_TYPE_FUNCTIONS; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index 87e5ba5730..57f6dd8002 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -17,12 +17,12 @@ class ScheduleMessages extends ScheduleBase public static function getSupportedResource(): string { - return SCHEDULE_TYPE_MESSAGE; + return SCHEDULE_RESOURCE_TYPE_MESSAGE; } public static function getCollectionId(): string { - return COLLECTION_TYPE_MESSAGES; + return RESOURCE_TYPE_MESSAGES; } protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void From 232b22b71eca4bc7b900b77b854dd539cfaf540d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 07:35:17 +0530 Subject: [PATCH 195/385] use constants --- app/controllers/api/messaging.php | 13 ++++++------- app/init/constants.php | 1 + src/Appwrite/Migration/Version/V19.php | 3 +-- .../Modules/Functions/Http/Executions/Create.php | 3 +-- .../Modules/Functions/Http/Executions/Delete.php | 3 +-- .../Modules/Functions/Http/Functions/Create.php | 3 +-- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index bde93305dd..fd1a92e364 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -10,7 +10,6 @@ use Appwrite\Extend\Exception; use Appwrite\Messaging\Status as MessageStatus; use Appwrite\Network\Validator\Email; use Appwrite\Permission; -use Appwrite\Platform\Tasks\ScheduleMessages; use Appwrite\Role; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; @@ -3103,7 +3102,7 @@ App::post('/v1/messaging/messages/email') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3245,7 +3244,7 @@ App::post('/v1/messaging/messages/sms') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3463,7 +3462,7 @@ App::post('/v1/messaging/messages/push') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3864,7 +3863,7 @@ App::patch('/v1/messaging/messages/email/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4085,7 +4084,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4259,7 +4258,7 @@ App::patch('/v1/messaging/messages/push/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleMessages::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_MESSAGES, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/app/init/constants.php b/app/init/constants.php index 5c75dc4ce2..74f04f25e9 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -271,6 +271,7 @@ const RESOURCE_TYPE_PROVIDERS = 'providers'; const RESOURCE_TYPE_TOPICS = 'topics'; const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers'; const RESOURCE_TYPE_MESSAGES = 'messages'; +const RESOURCE_TYPE_EXECUTIONS = 'executions'; // Resource types for Tokens const TOKENS_RESOURCE_TYPE_FILES = 'files'; diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 790a2fdfb7..f5892f07bd 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -3,7 +3,6 @@ namespace Appwrite\Migration\Version; use Appwrite\Migration\Migration; -use Appwrite\Platform\Tasks\ScheduleFunctions; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -732,7 +731,7 @@ class V19 extends Migration if (empty($document->getAttribute('scheduleId', null))) { $schedule = $this->dbForPlatform->createDocument('schedules', new Document([ 'region' => $this->project->getAttribute('region'), - 'resourceType' => ScheduleFunctions::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_FUNCTIONS, 'resourceId' => $document->getId(), 'resourceInternalId' => $document->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 1603e8f997..69af3b7d04 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -11,7 +11,6 @@ use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Functions\Validator\Headers; use Appwrite\Platform\Modules\Compute\Base; -use Appwrite\Platform\Tasks\ScheduleExecutions; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -313,7 +312,7 @@ class Create extends Base $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleExecutions::getSupportedResource(), + 'resourceType' => SCHEDULE_RESOURCE_TYPE_EXECUTION, 'resourceId' => $execution->getId(), 'resourceInternalId' => $execution->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php index 9c818cfacc..666cb8310c 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Functions\Http\Executions; use Appwrite\Event\Event; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Compute\Base; -use Appwrite\Platform\Tasks\ScheduleExecutions; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -100,7 +99,7 @@ class Delete extends Base if ($status === 'scheduled') { $schedule = $dbForPlatform->findOne('schedules', [ Query::equal('resourceId', [$execution->getId()]), - Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]), + Query::equal('resourceType', [SCHEDULE_RESOURCE_TYPE_EXECUTION]), Query::equal('active', [true]), ]); diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index ccf5abc373..13444634fb 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -11,7 +11,6 @@ use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Compute\Base; use Appwrite\Platform\Modules\Compute\Validator\Specification; -use Appwrite\Platform\Tasks\ScheduleFunctions; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -236,7 +235,7 @@ class Create extends Base $schedule = Authorization::skip( fn () => $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => ScheduleFunctions::getSupportedResource(), + 'resourceType' => RESOURCE_TYPE_FUNCTIONS, 'resourceId' => $function->getId(), 'resourceInternalId' => $function->getSequence(), 'resourceUpdatedAt' => DateTime::now(), From 734d2ce51240845abafe0e536f538a81a6ec164b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 07:49:07 +0530 Subject: [PATCH 196/385] fix: use correct constant --- app/controllers/api/messaging.php | 12 ++++++------ src/Appwrite/Migration/Version/V19.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index fd1a92e364..dbc384f3c4 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -3102,7 +3102,7 @@ App::post('/v1/messaging/messages/email') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3244,7 +3244,7 @@ App::post('/v1/messaging/messages/sms') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3462,7 +3462,7 @@ App::post('/v1/messaging/messages/push') case MessageStatus::SCHEDULED: $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -3863,7 +3863,7 @@ App::patch('/v1/messaging/messages/email/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4084,7 +4084,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), @@ -4258,7 +4258,7 @@ App::patch('/v1/messaging/messages/push/:messageId') if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_MESSAGES, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE, 'resourceId' => $message->getId(), 'resourceInternalId' => $message->getSequence(), 'resourceUpdatedAt' => DateTime::now(), diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index f5892f07bd..f5cf84c95e 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -731,7 +731,7 @@ class V19 extends Migration if (empty($document->getAttribute('scheduleId', null))) { $schedule = $this->dbForPlatform->createDocument('schedules', new Document([ 'region' => $this->project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_FUNCTIONS, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_FUNCTION, 'resourceId' => $document->getId(), 'resourceInternalId' => $document->getSequence(), 'resourceUpdatedAt' => DateTime::now(), From 421ec6f3f8c72eb528aecacae39be1556e16c763 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 07:49:55 +0530 Subject: [PATCH 197/385] fix: use correct constant --- .../Platform/Modules/Functions/Http/Functions/Create.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 13444634fb..b00a2ad2bf 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -235,7 +235,7 @@ class Create extends Base $schedule = Authorization::skip( fn () => $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), - 'resourceType' => RESOURCE_TYPE_FUNCTIONS, + 'resourceType' => SCHEDULE_RESOURCE_TYPE_FUNCTION, 'resourceId' => $function->getId(), 'resourceInternalId' => $function->getSequence(), 'resourceUpdatedAt' => DateTime::now(), From 84a4ef0bafe46163b5807db0781779e0eead6f00 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 10:47:39 +0530 Subject: [PATCH 198/385] chore: include response model enum names --- app/config/specs/open-api3-1.8.x-console.json | 39 +++++--- app/config/specs/open-api3-1.8.x-server.json | 39 +++++--- .../specs/open-api3-latest-console.json | 39 +++++--- app/config/specs/open-api3-latest-server.json | 39 +++++--- app/config/specs/swagger2-1.8.x-console.json | 39 +++++--- app/config/specs/swagger2-1.8.x-server.json | 39 +++++--- app/config/specs/swagger2-latest-console.json | 39 +++++--- app/config/specs/swagger2-latest-server.json | 39 +++++--- src/Appwrite/SDK/Specification/Format.php | 89 ++++++++++++++++++- .../SDK/Specification/Format/OpenAPI3.php | 14 ++- .../SDK/Specification/Format/Swagger2.php | 14 ++- 11 files changed, 319 insertions(+), 110 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 1dd58c3261..d75265f552 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -46468,7 +46468,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46563,7 +46564,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46660,7 +46662,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46757,7 +46760,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46837,7 +46841,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46924,7 +46929,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47021,7 +47027,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47108,7 +47115,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47195,7 +47203,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47282,7 +47291,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47397,7 +47407,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47483,7 +47494,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47581,7 +47593,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index b437d56bfc..3cb3f01ace 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -35338,7 +35338,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35433,7 +35434,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35530,7 +35532,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35627,7 +35630,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35707,7 +35711,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35794,7 +35799,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35891,7 +35897,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35978,7 +35985,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36065,7 +36073,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36152,7 +36161,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36267,7 +36277,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36353,7 +36364,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36451,7 +36463,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 1dd58c3261..d75265f552 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -46468,7 +46468,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46563,7 +46564,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46660,7 +46662,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46757,7 +46760,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46837,7 +46841,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46924,7 +46929,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47021,7 +47027,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47108,7 +47115,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47195,7 +47203,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47282,7 +47291,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47397,7 +47407,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47483,7 +47494,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47581,7 +47593,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index b437d56bfc..3cb3f01ace 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -35338,7 +35338,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35433,7 +35434,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35530,7 +35532,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35627,7 +35630,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35707,7 +35711,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35794,7 +35799,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35891,7 +35897,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35978,7 +35985,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36065,7 +36073,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36152,7 +36161,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36267,7 +36277,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36353,7 +36364,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36451,7 +36463,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 0f91a5433d..072da6a403 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -46405,7 +46405,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46500,7 +46501,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46597,7 +46599,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46694,7 +46697,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46774,7 +46778,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46861,7 +46866,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46958,7 +46964,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47045,7 +47052,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47132,7 +47140,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47219,7 +47228,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47334,7 +47344,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47420,7 +47431,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47518,7 +47530,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 679da75e5d..3e997b2341 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -35366,7 +35366,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35461,7 +35462,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35558,7 +35560,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35655,7 +35658,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35735,7 +35739,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35822,7 +35827,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35919,7 +35925,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36006,7 +36013,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36093,7 +36101,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36180,7 +36189,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36295,7 +36305,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36381,7 +36392,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36479,7 +36491,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 0f91a5433d..072da6a403 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -46405,7 +46405,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46500,7 +46501,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46597,7 +46599,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46694,7 +46697,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46774,7 +46778,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46861,7 +46866,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -46958,7 +46964,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47045,7 +47052,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47132,7 +47140,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47219,7 +47228,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47334,7 +47344,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47420,7 +47431,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -47518,7 +47530,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 679da75e5d..3e997b2341 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -35366,7 +35366,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35461,7 +35462,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35558,7 +35560,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35655,7 +35658,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35735,7 +35739,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35822,7 +35827,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -35919,7 +35925,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36006,7 +36013,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36093,7 +36101,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36180,7 +36189,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36295,7 +36305,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36381,7 +36392,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", @@ -36479,7 +36491,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "AttributeStatus" }, "error": { "type": "string", diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index 825f9bf01d..d52f36c40c 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -112,7 +112,7 @@ abstract class Format return $this->params[$key] ?? $default; } - protected function getEnumName(string $service, string $method, string $param): ?string + protected function getRequestEnumName(string $service, string $method, string $param): ?string { /* `$service` is `$namespace` */ switch ($service) { @@ -450,7 +450,7 @@ abstract class Format return null; } - public function getEnumKeys(string $service, string $method, string $param): array + public function getRequestEnumKeys(string $service, string $method, string $param): array { $values = []; switch ($service) { @@ -543,6 +543,91 @@ abstract class Format return $values; } + public function getResponseEnumName(string $model, string $param): ?string + { + switch ($model) { + case 'attributeString': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeInteger': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeFloat': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeBoolean': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeEmail': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeEnum': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeIp': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeUrl': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeDatetime': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeRelationship': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributePoint': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributeLine': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + case 'attributePolygon': + switch ($param) { + case 'status': + return 'AttributeStatus'; + } + break; + } + return null; + } + protected function getNestedModels(Model $model, array &$usedModels): void { foreach ($model->getRules() as $rule) { diff --git a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php index c11e55e733..2380f03920 100644 --- a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php @@ -9,6 +9,7 @@ use Appwrite\SDK\Response; use Appwrite\SDK\Specification\Format; use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; +use Appwrite\Utopia\Response\Model\Any; use Utopia\Database\Database; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -559,8 +560,8 @@ class OpenAPI3 extends Format if ($allowed) { $node['schema']['enum'] = $validator->getList(); - $node['schema']['x-enum-name'] = $this->getEnumName($sdk->getNamespace() ?? '', $methodName, $name); - $node['schema']['x-enum-keys'] = $this->getEnumKeys($sdk->getNamespace() ?? '', $methodName, $name); + $node['schema']['x-enum-name'] = $this->getRequestEnumName($sdk->getNamespace() ?? '', $methodName, $name); + $node['schema']['x-enum-keys'] = $this->getRequestEnumKeys($sdk->getNamespace() ?? '', $methodName, $name); } if ($validator->getType() === 'integer') { $node['format'] = 'int32'; @@ -777,8 +778,16 @@ class OpenAPI3 extends Format if ($rule['type'] === 'enum' && !empty($rule['enum'])) { if ($rule['array']) { $output['components']['schemas'][$model->getType()]['properties'][$name]['items']['enum'] = $rule['enum']; + $enumName = $this->getResponseEnumName($model->getType(), $name); + if ($enumName) { + $output['components']['schemas'][$model->getType()]['properties'][$name]['items']['x-enum-name'] = $enumName; + } } else { $output['components']['schemas'][$model->getType()]['properties'][$name]['enum'] = $rule['enum']; + $enumName = $this->getResponseEnumName($model->getType(), $name); + if ($enumName) { + $output['components']['schemas'][$model->getType()]['properties'][$name]['x-enum-name'] = $enumName; + } } } if (!in_array($name, $required)) { @@ -786,6 +795,7 @@ class OpenAPI3 extends Format } } + /** @var Any $model */ if ($model->isAny() && !empty($model->getSampleData())) { $examples = array_merge($examples, $model->getSampleData()); } diff --git a/src/Appwrite/SDK/Specification/Format/Swagger2.php b/src/Appwrite/SDK/Specification/Format/Swagger2.php index 4e784e8116..ed1217d86c 100644 --- a/src/Appwrite/SDK/Specification/Format/Swagger2.php +++ b/src/Appwrite/SDK/Specification/Format/Swagger2.php @@ -9,6 +9,7 @@ use Appwrite\SDK\Response; use Appwrite\SDK\Specification\Format; use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; +use Appwrite\Utopia\Response\Model\Any; use Utopia\Database\Database; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -565,8 +566,8 @@ class Swagger2 extends Format if ($allowed && $validator->getType() === 'string') { $node['enum'] = $validator->getList(); - $node['x-enum-name'] = $this->getEnumName($namespace, $methodName, $name); - $node['x-enum-keys'] = $this->getEnumKeys($namespace, $methodName, $name); + $node['x-enum-name'] = $this->getRequestEnumName($namespace, $methodName, $name); + $node['x-enum-keys'] = $this->getRequestEnumKeys($namespace, $methodName, $name); } if ($validator->getType() === 'integer') { @@ -799,8 +800,16 @@ class Swagger2 extends Format if ($rule['type'] === 'enum' && !empty($rule['enum'])) { if ($rule['array']) { $output['definitions'][$model->getType()]['properties'][$name]['items']['enum'] = $rule['enum']; + $enumName = $this->getResponseEnumName($model->getType(), $name); + if ($enumName) { + $output['definitions'][$model->getType()]['properties'][$name]['items']['x-enum-name'] = $enumName; + } } else { $output['definitions'][$model->getType()]['properties'][$name]['enum'] = $rule['enum']; + $enumName = $this->getResponseEnumName($model->getType(), $name); + if ($enumName) { + $output['definitions'][$model->getType()]['properties'][$name]['x-enum-name'] = $enumName; + } } } if (!in_array($name, $required)) { @@ -808,6 +817,7 @@ class Swagger2 extends Format } } + /** @var Any $model */ if ($model->isAny() && !empty($model->getSampleData())) { $examples = array_merge($examples, $model->getSampleData()); } From 17f1cb214fbea4784a316488224d6929a0bcc54d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 10:57:35 +0530 Subject: [PATCH 199/385] override healthstatus --- app/config/specs/open-api3-1.8.x-console.json | 3 ++- app/config/specs/open-api3-1.8.x-server.json | 3 ++- app/config/specs/open-api3-latest-console.json | 3 ++- app/config/specs/open-api3-latest-server.json | 3 ++- app/config/specs/swagger2-1.8.x-console.json | 3 ++- app/config/specs/swagger2-1.8.x-server.json | 3 ++- app/config/specs/swagger2-latest-console.json | 3 ++- app/config/specs/swagger2-latest-server.json | 3 ++- src/Appwrite/SDK/Specification/Format.php | 6 ++++++ 9 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index d75265f552..86b5f3ba6f 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -53809,7 +53809,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 3cb3f01ace..b9270ad857 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -40997,7 +40997,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index d75265f552..86b5f3ba6f 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -53809,7 +53809,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 3cb3f01ace..b9270ad857 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -40997,7 +40997,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 072da6a403..f682fc8670 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -53765,7 +53765,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 3e997b2341..bade0bae90 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -41034,7 +41034,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 072da6a403..f682fc8670 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -53765,7 +53765,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 3e997b2341..bade0bae90 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -41034,7 +41034,8 @@ "enum": [ "pass", "fail" - ] + ], + "x-enum-name": "HealthStatus" } }, "required": [ diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index d52f36c40c..7db5d5f559 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -624,6 +624,12 @@ abstract class Format return 'AttributeStatus'; } break; + case 'healthStatus': + switch ($param) { + case 'status': + return 'HealthStatus'; + } + break; } return null; } From 5d8ac0a5b0628b7b3b9b6b3eca4205eaf5420b91 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 11:31:29 +0530 Subject: [PATCH 200/385] add message status --- app/config/specs/open-api3-1.8.x-console.json | 9 ++++++++- app/config/specs/open-api3-1.8.x-server.json | 9 ++++++++- app/config/specs/open-api3-latest-console.json | 9 ++++++++- app/config/specs/open-api3-latest-server.json | 9 ++++++++- app/config/specs/swagger2-1.8.x-console.json | 9 ++++++++- app/config/specs/swagger2-1.8.x-server.json | 9 ++++++++- app/config/specs/swagger2-latest-console.json | 9 ++++++++- app/config/specs/swagger2-latest-server.json | 9 ++++++++- src/Appwrite/Utopia/Response/Model/Message.php | 6 ++++-- 9 files changed, 68 insertions(+), 10 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 86b5f3ba6f..31471b170c 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -56549,7 +56549,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index b9270ad857..259485c793 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -41463,7 +41463,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 86b5f3ba6f..31471b170c 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -56549,7 +56549,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index b9270ad857..259485c793 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -41463,7 +41463,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index f682fc8670..1abb7c4ef8 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -56598,7 +56598,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index bade0bae90..f350d10c54 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -41502,7 +41502,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index f682fc8670..1abb7c4ef8 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -56598,7 +56598,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index bade0bae90..f350d10c54 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -41502,7 +41502,14 @@ "status": { "type": "string", "description": "Status of delivery.", - "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed." + "x-example": "Message status can be one of the following: draft, processing, scheduled, sent, or failed.", + "enum": [ + "draft", + "processing", + "scheduled", + "sent", + "failed" + ] } }, "required": [ diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index e52b6836c5..4c1e08b9cb 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -34,6 +34,7 @@ class Message extends Model 'description' => 'Message provider type.', 'default' => '', 'example' => MESSAGE_TYPE_EMAIL, + 'enum' => [MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH], ]) ->addRule('topics', [ 'type' => self::TYPE_STRING, @@ -50,7 +51,7 @@ class Message extends Model 'example' => ['5e5ea5c16897e'], ]) ->addRule('targets', [ - 'type' => self::TYPE_STRING, + 'type' => self::TYPE_ENUM, 'description' => 'Target IDs set as recipients.', 'default' => '', 'array' => true, @@ -94,10 +95,11 @@ class Message extends Model ], ]) ->addRule('status', [ - 'type' => self::TYPE_STRING, + 'type' => self::TYPE_ENUM, 'description' => 'Status of delivery.', 'default' => 'draft', 'example' => 'Message status can be one of the following: draft, processing, scheduled, sent, or failed.', + 'enum' => ['draft', 'processing', 'scheduled', 'sent', 'failed'], ]); } From 0995bd6a6edfd49e3138c1a2e172eace7c11de61 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 23 Sep 2025 19:17:06 +1200 Subject: [PATCH 201/385] SDK releases --- app/config/platforms.php | 28 +++--- composer.lock | 94 +++++++++++-------- .../functions/create-duplicate-deployment.md | 2 +- .../functions/create-template-deployment.md | 2 +- .../functions/create-vcs-deployment.md | 2 +- .../functions/update-deployment-status.md | 2 +- .../sites/create-duplicate-deployment.md | 2 +- .../sites/create-template-deployment.md | 2 +- .../examples/sites/create-vcs-deployment.md | 2 +- .../sites/update-deployment-status.md | 2 +- docs/sdks/dart/CHANGELOG.md | 4 + docs/sdks/flutter/CHANGELOG.md | 4 + 12 files changed, 83 insertions(+), 63 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 7aec82d1cf..8a33144b2f 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '20.0.0', + 'version' => '20.1.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '19.0.0', + 'version' => '19.1.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '12.0.0', + 'version' => '12.1.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '10.0.0', + 'version' => '10.1.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -139,7 +139,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.13.0', + 'version' => '0.14.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '19.0.0', + 'version' => '19.1.0', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -281,7 +281,7 @@ return [ [ 'key' => 'php', 'name' => 'PHP', - 'version' => '17.0.0', + 'version' => '17.1.0', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -300,7 +300,7 @@ return [ [ 'key' => 'python', 'name' => 'Python', - 'version' => '13.0.0', + 'version' => '13.1.0', 'url' => 'https://github.com/appwrite/sdk-for-python', 'package' => 'https://pypi.org/project/appwrite/', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '18.0.0', + 'version' => '18.1.0', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => '0.11.0', + 'version' => '0.12.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.17.0', + 'version' => '0.18.0', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '18.0.0', + 'version' => '18.1.0', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '11.0.0', + 'version' => '11.1.0', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '12.0.0', + 'version' => '12.1.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 98089f9ed9..b9475de5b9 100644 --- a/composer.lock +++ b/composer.lock @@ -1159,20 +1159,20 @@ }, { "name": "open-telemetry/api", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5" + "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/ee17d937652eca06c2341b6fadc0f74c1c1a5af2", + "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2", "shasum": "" }, "require": { - "open-telemetry/context": "^1.0", + "open-telemetry/context": "^1.4", "php": "^8.1", "psr/log": "^1.1|^2.0|^3.0", "symfony/polyfill-php82": "^1.26" @@ -1225,20 +1225,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-07T23:07:38+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/context", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", "shasum": "" }, "require": { @@ -1284,7 +1284,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-13T01:12:00+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -1415,23 +1415,23 @@ }, { "name": "open-telemetry/sdk", - "version": "1.7.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06" + "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/52690d4b37ae4f091af773eef3c238ed2bc0aa06", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/105c6e81e3d86150bd5704b00c7e4e165e957b89", + "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.4", - "open-telemetry/context": "^1.0", + "open-telemetry/api": "^1.6", + "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", "php-http/discovery": "^1.14", @@ -1508,7 +1508,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-05T07:17:06+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1569,16 +1569,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/ce27936c8dfb73e3ab9c94469130428af9752c96", + "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96", "shasum": "" }, "require": { @@ -1632,7 +1632,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2025-09-22T20:41:46+00:00" }, { "name": "paragonie/random_compat", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.3.5", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "6fda9e58b37c9872c1a2a424e5467de8de1bc567" + "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6fda9e58b37c9872c1a2a424e5467de8de1bc567", - "reference": "6fda9e58b37c9872c1a2a424e5467de8de1bc567", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3583fa6fddb1d1a902b37ff2048527a5827fc008", + "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.3.5" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.0" }, - "time": "2025-09-15T04:19:40+00:00" + "time": "2025-09-23T02:27:10+00:00" }, { "name": "doctrine/annotations", @@ -5278,16 +5278,16 @@ }, { "name": "laravel/pint", - "version": "v1.25.0", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "595de38458c6b0ab4cae4bcc769c2e5c5d5b8e96" + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/595de38458c6b0ab4cae4bcc769c2e5c5d5b8e96", - "reference": "595de38458c6b0ab4cae4bcc769c2e5c5d5b8e96", + "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", "shasum": "" }, "require": { @@ -5340,7 +5340,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-09-17T01:36:44+00:00" + "time": "2025-09-19T02:57:12+00:00" }, { "name": "matthiasmullie/minify", @@ -6829,16 +6829,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "eb49b981ef0817890129cb70f774506bebe57740" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/eb49b981ef0817890129cb70f774506bebe57740", + "reference": "eb49b981ef0817890129cb70f774506bebe57740", "shasum": "" }, "require": { @@ -6894,15 +6894,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.7" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-22T05:18:21+00:00" }, { "name": "sebastian/global-state", @@ -8506,7 +8518,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docs/examples/1.8.x/server-graphql/examples/functions/create-duplicate-deployment.md b/docs/examples/1.8.x/server-graphql/examples/functions/create-duplicate-deployment.md index cdd92c2a02..bc3587fcec 100644 --- a/docs/examples/1.8.x/server-graphql/examples/functions/create-duplicate-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/functions/create-duplicate-deployment.md @@ -24,12 +24,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-graphql/examples/functions/create-template-deployment.md index 12c50c32f3..0ce968e5f4 100644 --- a/docs/examples/1.8.x/server-graphql/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/functions/create-template-deployment.md @@ -27,12 +27,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-graphql/examples/functions/create-vcs-deployment.md index ebfced2c68..60a78c41ca 100644 --- a/docs/examples/1.8.x/server-graphql/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/functions/create-vcs-deployment.md @@ -25,12 +25,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/functions/update-deployment-status.md b/docs/examples/1.8.x/server-graphql/examples/functions/update-deployment-status.md index 50df97fd21..68735b35ca 100644 --- a/docs/examples/1.8.x/server-graphql/examples/functions/update-deployment-status.md +++ b/docs/examples/1.8.x/server-graphql/examples/functions/update-deployment-status.md @@ -23,12 +23,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/sites/create-duplicate-deployment.md b/docs/examples/1.8.x/server-graphql/examples/sites/create-duplicate-deployment.md index 6226282651..1b2d3dc131 100644 --- a/docs/examples/1.8.x/server-graphql/examples/sites/create-duplicate-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/sites/create-duplicate-deployment.md @@ -23,12 +23,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-graphql/examples/sites/create-template-deployment.md index 72562556e4..f63d8c5e5a 100644 --- a/docs/examples/1.8.x/server-graphql/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/sites/create-template-deployment.md @@ -27,12 +27,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-graphql/examples/sites/create-vcs-deployment.md index ccc18cf2e0..6c5241e734 100644 --- a/docs/examples/1.8.x/server-graphql/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-graphql/examples/sites/create-vcs-deployment.md @@ -25,12 +25,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/examples/1.8.x/server-graphql/examples/sites/update-deployment-status.md b/docs/examples/1.8.x/server-graphql/examples/sites/update-deployment-status.md index 92751c167f..24064428e2 100644 --- a/docs/examples/1.8.x/server-graphql/examples/sites/update-deployment-status.md +++ b/docs/examples/1.8.x/server-graphql/examples/sites/update-deployment-status.md @@ -23,12 +23,12 @@ mutation { providerRepositoryName providerRepositoryOwner providerRepositoryUrl - providerBranch providerCommitHash providerCommitAuthorUrl providerCommitAuthor providerCommitMessage providerCommitUrl + providerBranch providerBranchUrl } } diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index 7033bbdd1d..7e33794153 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 18.1.0 + +* Add `orderRandom` query support + ## 18.0.0 * Rename `CreditCard` enum value `unionChinaPay` to `unionPay` diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index 7ff4a445b3..f704415675 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 19.1.0 + +* Add `orderRandom` query support + ## 19.0.0 * Rename `CreditCard` enum value `unionChinaPay` to `unionPay` From 9ede8cfb91bb59b748d87b94fa5e30525192a678 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 16:52:35 +0530 Subject: [PATCH 202/385] fix: healthstatus enum override due to conflict --- app/config/specs/open-api3-1.8.x-console.json | 2 +- app/config/specs/open-api3-1.8.x-server.json | 2 +- app/config/specs/open-api3-latest-console.json | 2 +- app/config/specs/open-api3-latest-server.json | 2 +- app/config/specs/swagger2-1.8.x-console.json | 2 +- app/config/specs/swagger2-1.8.x-server.json | 2 +- app/config/specs/swagger2-latest-console.json | 2 +- app/config/specs/swagger2-latest-server.json | 2 +- src/Appwrite/SDK/Specification/Format.php | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 31471b170c..ab36ae475c 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -53810,7 +53810,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 259485c793..93a0d9d46b 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -40998,7 +40998,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 31471b170c..ab36ae475c 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -53810,7 +53810,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 259485c793..93a0d9d46b 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -40998,7 +40998,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 1abb7c4ef8..e830f768be 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -53766,7 +53766,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index f350d10c54..0a5fc8fe95 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -41035,7 +41035,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 1abb7c4ef8..e830f768be 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -53766,7 +53766,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index f350d10c54..0a5fc8fe95 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -41035,7 +41035,7 @@ "pass", "fail" ], - "x-enum-name": "HealthStatus" + "x-enum-name": "HealthCheckStatus" } }, "required": [ diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index 7db5d5f559..db7335e40f 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -627,7 +627,7 @@ abstract class Format case 'healthStatus': switch ($param) { case 'status': - return 'HealthStatus'; + return 'HealthCheckStatus'; } break; } From 7ea01733ef3d565198df0f012eb1ac88523e764e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 23 Sep 2025 18:28:29 +0530 Subject: [PATCH 203/385] chore: update afterbuild fn --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9547a752ef..72736a2c9b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -899,7 +899,7 @@ class Builds extends Action Console::log('Build details stored'); - $this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment); + $this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment, $runtime); $logs = $deployment->getAttribute('buildLogs', ''); /** Screenshot site */ @@ -1392,11 +1392,12 @@ class Builds extends Action * @param Document $deployment * @return void */ - protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment): void + protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime): void { assert($queueForRealtime instanceof Realtime); assert($dbForProject instanceof Database); assert($deployment instanceof Document); + assert(is_array($runtime)); } protected function getRuntime(Document $resource, string $version): array From 5749b4175356a217f740b584ae3741c2cf7b7cc3 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 24 Sep 2025 10:00:55 +0530 Subject: [PATCH 204/385] updated to use if throw checks --- .../Modules/Functions/Workers/Builds.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 72736a2c9b..f2f3b132aa 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -1391,13 +1391,22 @@ class Builds extends Action * @param Database $dbForProject * @param Document $deployment * @return void + * @throws Exception */ protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime): void { - assert($queueForRealtime instanceof Realtime); - assert($dbForProject instanceof Database); - assert($deployment instanceof Document); - assert(is_array($runtime)); + if (!($queueForRealtime instanceof Realtime)) { + throw new Exception('queueForRealtime must be an instance of Realtime'); + } + if (!($dbForProject instanceof Database)) { + throw new Exception('dbForProject must be an instance of Database'); + } + if (!($deployment instanceof Document)) { + throw new Exception('deployment must be an instance of Document'); + } + if (!is_array($runtime)) { + throw new Exception('runtime must be an array'); + } } protected function getRuntime(Document $resource, string $version): array From 9d99f5cec254a531f57802e1e75d0ba3149cbc8f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 24 Sep 2025 11:47:13 +0530 Subject: [PATCH 205/385] chore: update afterbuild pass adapter --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index f2f3b132aa..9dc70718a5 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -899,7 +899,7 @@ class Builds extends Action Console::log('Build details stored'); - $this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment, $runtime); + $this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment, $runtime, $adapter); $logs = $deployment->getAttribute('buildLogs', ''); /** Screenshot site */ @@ -1393,7 +1393,7 @@ class Builds extends Action * @return void * @throws Exception */ - protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime): void + protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime, string $adapter): void { if (!($queueForRealtime instanceof Realtime)) { throw new Exception('queueForRealtime must be an instance of Realtime'); @@ -1407,6 +1407,9 @@ class Builds extends Action if (!is_array($runtime)) { throw new Exception('runtime must be an array'); } + if (!is_string($adapter)) { + throw new Exception('adapter must be a string'); + } } protected function getRuntime(Document $resource, string $version): array From 85da691aecfb9434cd1062b5cd47536845ffc79d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 24 Sep 2025 13:05:37 +0530 Subject: [PATCH 206/385] allow null adapter --- .../Platform/Modules/Functions/Workers/Builds.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9dc70718a5..ce45d6b629 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -1390,10 +1390,12 @@ class Builds extends Action * @param Realtime $queueForRealtime * @param Database $dbForProject * @param Document $deployment + * @param array $runtime + * @param string|null $adapter * @return void * @throws Exception */ - protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime, string $adapter): void + protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime, ?string $adapter): void { if (!($queueForRealtime instanceof Realtime)) { throw new Exception('queueForRealtime must be an instance of Realtime'); @@ -1407,8 +1409,8 @@ class Builds extends Action if (!is_array($runtime)) { throw new Exception('runtime must be an array'); } - if (!is_string($adapter)) { - throw new Exception('adapter must be a string'); + if (!is_string($adapter) && !is_null($adapter)) { + throw new Exception('adapter must be a string or null'); } } From 752368327f63694945de30f2661e1c041f24cb03 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Wed, 24 Sep 2025 13:06:20 +0530 Subject: [PATCH 207/385] feedback --- .../locale/templates/email-mfa-challenge.tpl | 2 +- app/config/locale/templates/email-otp.tpl | 2 +- app/controllers/api/account.php | 63 ++++++++++++------- tests/e2e/Services/Account/AccountBase.php | 2 + 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl index fdc0f4d498..a828e3d299 100644 --- a/app/config/locale/templates/email-mfa-challenge.tpl +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -5,7 +5,7 @@
-
{{otp}}
+

{{otp}}

diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index aebc512e1b..e18a4ce725 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -5,7 +5,7 @@
-
{{otp}}
+

{{otp}}

diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index bde3672774..3aa9678a72 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2400,16 +2400,21 @@ App::post('/v1/account/tokens/email') 'phrase' => !empty($phrase) ? $phrase : '', // TODO: remove unnecessary team variable from this email 'team' => '', - 'heading' => $heading, - 'accentColor' => APP_EMAIL_ACCENT_COLOR, - 'logoUrl' => APP_EMAIL_LOGO_URL, - 'twitterUrl' => APP_SOCIAL_TWITTER, - 'discordUrl' => APP_SOCIAL_DISCORD, - 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, - 'termsUrl' => APP_EMAIL_TERMS_URL, - 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; + if ($customEmails && !empty($customTemplate)) { + $emailVariables = array_merge($emailVariables, [ + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, + ]); + } + $queueForMails ->setSubject($subject) ->setPreview($preview) @@ -3706,16 +3711,21 @@ App::post('/v1/account/verification') 'project' => $projectName, // TODO: remove unnecessary team variable from this email 'team' => '', - 'heading' => $heading, - 'accentColor' => APP_EMAIL_ACCENT_COLOR, - 'logoUrl' => APP_EMAIL_LOGO_URL, - 'twitterUrl' => APP_SOCIAL_TWITTER, - 'discordUrl' => APP_SOCIAL_DISCORD, - 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, - 'termsUrl' => APP_EMAIL_TERMS_URL, - 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; + if ($customEmails && !empty($customTemplate)) { + $emailVariables = array_merge($emailVariables, [ + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, + ]); + } + $queueForMails ->setSubject($subject) ->setPreview($preview) @@ -4817,16 +4827,21 @@ App::post('/v1/account/mfa/challenge') 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', - 'heading' => $heading, - 'accentColor' => APP_EMAIL_ACCENT_COLOR, - 'logoUrl' => APP_EMAIL_LOGO_URL, - 'twitterUrl' => APP_SOCIAL_TWITTER, - 'discordUrl' => APP_SOCIAL_DISCORD, - 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, - 'termsUrl' => APP_EMAIL_TERMS_URL, - 'privacyUrl' => APP_EMAIL_PRIVACY_URL, ]; + if ($customEmails && !empty($customTemplate)) { + $emailVariables = array_merge($emailVariables, [ + 'heading' => $heading, + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, + ]); + } + $queueForMails ->setSubject($subject) ->setPreview($preview) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 6cf997e22c..46283e8f86 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -188,6 +188,8 @@ trait AccountBase // Only Console project has branded logo in email. if ($isConsoleProject) { $this->assertStringContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']); + } else { + $this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']); } // TODO: Remove this once OTP login is supported for Console. From c00eec948a32befb81968772342cbe55d3665309 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 24 Sep 2025 18:01:43 +0530 Subject: [PATCH 208/385] remove: unnecessary changes. --- .github/labeler.yml | 83 -------------------------- .github/workflows/auto-label-issue.yml | 22 ------- README.md | 2 +- 3 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 .github/labeler.yml delete mode 100644 .github/workflows/auto-label-issue.yml diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index fb46eb5ba1..0000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,83 +0,0 @@ -# Fixes and upgrades for the Appwrite Auth / Users / Teams services. -"product / auth": - - "(auth|session|login|logout|register|2fa|mfa|users|teams|memberships|invite|oauth|oauth2|sso|jwt)" - -# Fixes and upgrades for the Appwrite Realtime API. -"api / realtime": - - "(realtime|subscribe|websockets)" - -# Console, UI and UX issues -"product / console": - - "(console)" - -# Fixes and upgrades for the Appwrite Storage. -"product / storage": - - "(storage|bucket|file|image|preview|download)" - -# Fixes and upgrades for the Appwrite Database. -"product / databases": - - "(database|collection|tables|attribute|column|document|row|query|queries|indexes|search|filter|sort|pagination)" - -# Fixes and upgrades for the Appwrite Functions. -"product / functions": - - "(function|runtime|deployment|execution|trigger|cron|schedule)" - -# Fixes and upgrades for the Appwrite Docs. -# "product / docs": -# - - -# Fixes and upgrades for the Appwrite Migrations. -"product / migrations": - - "(migrate|migration)" - -# Fixes and upgrades for the Appwrite Messaging. -"product / messaging": - - "(messaging|email|sms|push|provider|topic|target|notification)" - -# Fixes and upgrades for the Appwrite Platform. -# "product / platform": -# - - -# Fixes and upgrades for database relationships -"feature / relationships": - - "(relationship)" - -# Issues found only on Appwrite Cloud -# "product / cloud": -# - - -# Fixes and upgrades for the Appwrite VCS. -"product / vcs": - - "(repo|push|vcs|repository)" - -# Fixes and upgrades for the Appwrite GraphQL API. -"api / graphql": - - "(graphql|gql|mutation)" - -# Fixes and upgrades for the Appwrite Assistant. -"product / assistant": - - "(assistant)" - -# Fixes and upgrades for the Appwrite Domains. -"product / domains": - - "(domain|dns|ssl|certificate)" - -# Fixes and upgrades for the Appwrite Locale. -"product / locale": - - "(locale|i18n|internationalization|localization|l10n|translation|timezone|country)" - -# Fixes and upgrades for the Appwrite Avatars. -"product / avatars": - - "(avatar|initial|flag|icon)" - -# Fixes and upgrades for Appwrite Sites. -"product / sites": - - "(site|web|hosting|domain|ssl|certificate|nextjs|nuxt|react|angular|vue|svelte|astro)" - -# Fixes and upgrades for the Appwrite CLI. -"sdk / cli": - - "(cli|command line)" - -# Issues only found when self-hosting Appwrite -"product / self-hosted": - - "(self-host|self host)" diff --git a/.github/workflows/auto-label-issue.yml b/.github/workflows/auto-label-issue.yml deleted file mode 100644 index e0eb0de98d..0000000000 --- a/.github/workflows/auto-label-issue.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Auto Label Issue - -on: - issues: - types: [opened] - -permissions: - issues: write - contents: read - -jobs: - labeler: - runs-on: ubuntu-latest - steps: - - name: Issue Labeler - uses: github/issue-labeler@v3.4 - with: - configuration-path: .github/labeler.yml - enable-versioned-regex: false - include-title: 1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index c9b8a39fb2..9cd5808e33 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> We just announced API for spatial columns for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-spatial-columns) +> We just announced Timestamp Overrides for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-timestamp-overrides) > Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga) From 4a3cbdafca1deeb3ccd4b71254fa55eb9047e942 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Sep 2025 17:59:39 +1200 Subject: [PATCH 209/385] Move to row-level scope for txn --- app/config/roles.php | 4 ---- app/config/scopes.php | 6 ------ app/config/specs/open-api3-1.8.x-client.json | 20 +++++++++---------- app/config/specs/open-api3-1.8.x-console.json | 20 +++++++++---------- app/config/specs/open-api3-1.8.x-server.json | 20 +++++++++---------- app/config/specs/open-api3-latest-client.json | 20 +++++++++---------- .../specs/open-api3-latest-console.json | 20 +++++++++---------- app/config/specs/open-api3-latest-server.json | 20 +++++++++---------- app/config/specs/swagger2-1.8.x-client.json | 20 +++++++++---------- app/config/specs/swagger2-1.8.x-console.json | 20 +++++++++---------- app/config/specs/swagger2-1.8.x-server.json | 20 +++++++++---------- app/config/specs/swagger2-latest-client.json | 20 +++++++++---------- app/config/specs/swagger2-latest-console.json | 20 +++++++++---------- app/config/specs/swagger2-latest-server.json | 20 +++++++++---------- .../Http/Databases/Transactions/Create.php | 2 +- .../Http/Databases/Transactions/Delete.php | 2 +- .../Transactions/Operations/Create.php | 2 +- .../Http/Databases/Transactions/Update.php | 2 +- .../Http/TablesDB/Transactions/Create.php | 2 +- .../Http/TablesDB/Transactions/Delete.php | 2 +- .../Http/TablesDB/Transactions/Get.php | 2 +- .../Transactions/Operations/Create.php | 2 +- .../Http/TablesDB/Transactions/Update.php | 2 +- .../Http/TablesDB/Transactions/XList.php | 2 +- tests/e2e/Scopes/ProjectCustom.php | 2 -- 25 files changed, 130 insertions(+), 142 deletions(-) diff --git a/app/config/roles.php b/app/config/roles.php index 6a4bba2699..0f0945a2b4 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -16,8 +16,6 @@ $member = [ 'documents.write', 'rows.read', 'rows.write', - 'transactions.read', - 'transactions.write', 'files.read', 'files.write', 'projects.read', @@ -43,8 +41,6 @@ $admins = [ 'documents.write', 'rows.read', 'rows.write', - 'transactions.read', - 'transactions.write', 'files.read', 'files.write', 'buckets.read', diff --git a/app/config/scopes.php b/app/config/scopes.php index 9807c09216..d90ca2eabf 100644 --- a/app/config/scopes.php +++ b/app/config/scopes.php @@ -52,12 +52,6 @@ return [ // List of publicly visible scopes 'indexes.write' => [ 'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes', ], - 'transactions.read' => [ - 'description' => 'Access to read your project\'s database transactions', - ], - 'transactions.write' => [ - 'description' => 'Access to create, update, and delete your project\'s database transactions', - ], 'documents.read' => [ 'description' => 'Access to read your project\'s database documents', ], diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index 6d5acf8c86..4fd19f7d6a 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -4903,7 +4903,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5033,7 +5033,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5109,7 +5109,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5173,7 +5173,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -7980,7 +7980,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -8045,7 +8045,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8113,7 +8113,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -8175,7 +8175,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8251,7 +8251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8315,7 +8315,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 797a7e72e4..fe014dcd4c 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -5302,7 +5302,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5432,7 +5432,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5508,7 +5508,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5572,7 +5572,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -33372,7 +33372,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -33437,7 +33437,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33505,7 +33505,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -33567,7 +33567,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33643,7 +33643,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33707,7 +33707,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 8e8c33ace4..16ae5e57f9 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -4842,7 +4842,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -4976,7 +4976,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5054,7 +5054,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5120,7 +5120,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -23888,7 +23888,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -23955,7 +23955,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24025,7 +24025,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -24089,7 +24089,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24167,7 +24167,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24233,7 +24233,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 6d5acf8c86..4fd19f7d6a 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -4903,7 +4903,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5033,7 +5033,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5109,7 +5109,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5173,7 +5173,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -7980,7 +7980,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -8045,7 +8045,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8113,7 +8113,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -8175,7 +8175,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8251,7 +8251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -8315,7 +8315,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 797a7e72e4..fe014dcd4c 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -5302,7 +5302,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5432,7 +5432,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5508,7 +5508,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5572,7 +5572,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -33372,7 +33372,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -33437,7 +33437,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33505,7 +33505,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -33567,7 +33567,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33643,7 +33643,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -33707,7 +33707,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 8e8c33ace4..16ae5e57f9 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -4842,7 +4842,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -4976,7 +4976,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5054,7 +5054,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -5120,7 +5120,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client", @@ -23888,7 +23888,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -23955,7 +23955,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24025,7 +24025,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -24089,7 +24089,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24167,7 +24167,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", @@ -24233,7 +24233,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index e87ce88a6c..1aa47cb4fc 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -5045,7 +5045,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5174,7 +5174,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5251,7 +5251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5314,7 +5314,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -8057,7 +8057,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -8122,7 +8122,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8190,7 +8190,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -8251,7 +8251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8328,7 +8328,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8391,7 +8391,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 16744679ec..f88b1254cd 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -5464,7 +5464,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5593,7 +5593,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5670,7 +5670,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5733,7 +5733,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -33491,7 +33491,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -33556,7 +33556,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33624,7 +33624,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -33685,7 +33685,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33762,7 +33762,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33825,7 +33825,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index d3f7129c95..40f38c1067 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -4992,7 +4992,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5125,7 +5125,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5204,7 +5204,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5269,7 +5269,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -24063,7 +24063,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -24130,7 +24130,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24200,7 +24200,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -24263,7 +24263,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24342,7 +24342,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24407,7 +24407,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index e87ce88a6c..1aa47cb4fc 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -5045,7 +5045,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5174,7 +5174,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5251,7 +5251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5314,7 +5314,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -8057,7 +8057,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -8122,7 +8122,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8190,7 +8190,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -8251,7 +8251,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8328,7 +8328,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -8391,7 +8391,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 16744679ec..f88b1254cd 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -5464,7 +5464,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5593,7 +5593,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5670,7 +5670,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5733,7 +5733,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -33491,7 +33491,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -33556,7 +33556,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33624,7 +33624,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -33685,7 +33685,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33762,7 +33762,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -33825,7 +33825,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index d3f7129c95..40f38c1067 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -4992,7 +4992,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5125,7 +5125,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5204,7 +5204,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -5269,7 +5269,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "documents.write", "platforms": [ "server", "client" @@ -24063,7 +24063,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -24130,7 +24130,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24200,7 +24200,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -24263,7 +24263,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24342,7 +24342,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" @@ -24407,7 +24407,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.write", + "scope": "rows.write", "platforms": [ "server", "client" diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index 4a7e95d5ca..a5866ba7d1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -33,7 +33,7 @@ class Create extends Action ->setHttpPath('/v1/databases/transactions') ->desc('Create transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php index da92ce1b4c..a5d2562572 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Delete.php @@ -32,7 +32,7 @@ class Delete extends Action ->setHttpPath('/v1/databases/transactions/:transactionId') ->desc('Delete transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index d2e438706a..0aca3e3f7c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -38,7 +38,7 @@ class Create extends Action ->setHttpPath('/v1/databases/transactions/:transactionId/operations') ->desc('Create operations scoped to a transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 4509114bfc..9f5e5037bf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -44,7 +44,7 @@ class Update extends Action ->setHttpPath('/v1/databases/transactions/:transactionId') ->desc('Update transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php index 6a28d31621..e6c24b3341 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php @@ -30,7 +30,7 @@ class Create extends TransactionsCreate ->setHttpPath('/v1/tablesdb/transactions') ->desc('Create transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php index cad11e795a..28f273f566 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php @@ -30,7 +30,7 @@ class Delete extends TransactionsDelete ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Delete transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php index 39f6754a1e..bc58783a04 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php @@ -30,7 +30,7 @@ class Get extends TransactionsGet ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Get transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.read') + ->label('scope', 'rows.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index 2280a6f7e3..eab0fed161 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -32,7 +32,7 @@ class Create extends OperationsCreate ->setHttpPath('/v1/tablesdb/transactions/:transactionId/operations') ->desc('Create operations scoped to a transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index d76de0766d..bcfb2db406 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -31,7 +31,7 @@ class Update extends TransactionsUpdate ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Update transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.write') + ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php index 67a58d7e5f..cfb630e46d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php @@ -30,7 +30,7 @@ class XList extends TransactionsList ->setHttpPath('/v1/tablesdb/transactions') ->desc('List transactions') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.read') + ->label('scope', 'rows.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 49aabaae67..c2b4896814 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -70,8 +70,6 @@ trait ProjectCustom 'databases.write', 'collections.read', 'collections.write', - 'transactions.read', - 'transactions.write', 'tables.read', 'tables.write', 'documents.read', From 7a03084e832a57327e8d3244c25cef1122b98fd5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Sep 2025 19:21:21 +1200 Subject: [PATCH 210/385] Bump SDK version --- app/config/platforms.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 1eb8891b8a..fa2ec09ee2 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '20.1.0-rc.1', + 'version' => '20.2.0-rc.1', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '19.1.0-rc.1', + 'version' => '19.2.0-rc.1', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, From e06c358db494943af8324015059afd194661be23 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 00:50:25 +1200 Subject: [PATCH 211/385] Don't require data for delete operation --- .../Utopia/Database/Validator/Operation.php | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 4632678940..6bc068a60f 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -24,6 +24,20 @@ class Operation extends Validator 'decrement' => true, ]; + /** @var array */ + private array $requiresData = [ + 'create' => true, + 'update' => true, + 'upsert' => true, + 'delete' => false, // Delete doesn't need data + 'increment' => true, + 'decrement' => true, + 'bulkCreate' => true, + 'bulkUpdate' => true, + 'bulkUpsert' => true, + 'bulkDelete' => true, + ]; + /** @var array */ private array $actions = [ 'create' => true, @@ -121,14 +135,23 @@ class Operation extends Validator return false; } - // Data must be present and must be array (can be empty) - if (!\array_key_exists('data', $value)) { - $this->description = "Missing required key: data"; - return false; - } - if (!\is_array($value['data'])) { - $this->description = "Key 'data' must be an array"; - return false; + // Data validation - only required for certain actions + if (isset($this->requiresData[$value['action']]) && $this->requiresData[$value['action']]) { + // Data is required for this action + if (!\array_key_exists('data', $value)) { + $this->description = "Missing required key: data"; + return false; + } + if (!\is_array($value['data'])) { + $this->description = "Key 'data' must be an array"; + return false; + } + } else if (\array_key_exists('data', $value)) { + // Data is optional but if provided, must be an array + if (!\is_array($value['data'])) { + $this->description = "Key 'data' must be an array"; + return false; + } } return true; From 14c64e405b582cb55450444e5a29e71af6d28e47 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 25 Sep 2025 19:45:32 +0530 Subject: [PATCH 212/385] chore: update framework lib --- composer.lock | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/composer.lock b/composer.lock index b9475de5b9..f5a8c43b32 100644 --- a/composer.lock +++ b/composer.lock @@ -1569,16 +1569,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.8.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96" + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/ce27936c8dfb73e3ab9c94469130428af9752c96", - "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226", + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226", "shasum": "" }, "require": { @@ -1632,7 +1632,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2025-09-22T20:41:46+00:00" + "time": "2025-09-24T15:12:37+00:00" }, { "name": "paragonie/random_compat", @@ -3939,16 +3939,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.27", + "version": "0.33.28", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37" + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/d9d10a895e85c8c7675220347cc6109db9d3bd37", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", "shasum": "" }, "require": { @@ -3980,9 +3980,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.27" + "source": "https://github.com/utopia-php/http/tree/0.33.28" }, - "time": "2025-09-07T18:40:53+00:00" + "time": "2025-09-25T10:44:24+00:00" }, { "name": "utopia-php/image", @@ -4187,16 +4187,16 @@ }, { "name": "utopia-php/migration", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4" + "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/c42935a6a4ee3701c68d24244e82ecb39e945ec4", - "reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/42ff497c5231f5a727d1e229419ff1d2195d8093", + "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093", "shasum": "" }, "require": { @@ -4237,9 +4237,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.1.1" + "source": "https://github.com/utopia-php/migration/tree/1.2.0" }, - "time": "2025-09-10T06:17:20+00:00" + "time": "2025-09-24T10:32:24+00:00" }, { "name": "utopia-php/orchestration", @@ -6230,16 +6230,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.27", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -6264,7 +6264,7 @@ "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", + "sebastian/exporter": "^4.0.8", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -6313,7 +6313,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -6337,7 +6337,7 @@ "type": "tidelift" } ], - "time": "2025-09-14T06:18:03+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "psr/cache", @@ -6829,16 +6829,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.7", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "eb49b981ef0817890129cb70f774506bebe57740" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/eb49b981ef0817890129cb70f774506bebe57740", - "reference": "eb49b981ef0817890129cb70f774506bebe57740", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -6894,7 +6894,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.7" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { @@ -6914,7 +6914,7 @@ "type": "tidelift" } ], - "time": "2025-09-22T05:18:21+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", From 4e91904608d4d1269a26458e9c2ab229976378e9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 14:25:02 +1200 Subject: [PATCH 213/385] Update database --- composer.lock | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/composer.lock b/composer.lock index 1ff64ab5bc..d8fb2b1b4a 100644 --- a/composer.lock +++ b/composer.lock @@ -1569,16 +1569,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.8.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96" + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/ce27936c8dfb73e3ab9c94469130428af9752c96", - "reference": "ce27936c8dfb73e3ab9c94469130428af9752c96", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226", + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226", "shasum": "" }, "require": { @@ -1632,7 +1632,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2025-09-22T20:41:46+00:00" + "time": "2025-09-24T15:12:37+00:00" }, { "name": "paragonie/random_compat", @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "732ffef52fd76483722537ee1f4f83726125a7ec" + "reference": "19a3ab2ae99578861dd1a7b4fdbfb62b37d09447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/732ffef52fd76483722537ee1f4f83726125a7ec", - "reference": "732ffef52fd76483722537ee1f4f83726125a7ec", + "url": "https://api.github.com/repos/utopia-php/database/zipball/19a3ab2ae99578861dd1a7b4fdbfb62b37d09447", + "reference": "19a3ab2ae99578861dd1a7b4fdbfb62b37d09447", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.1.0" + "source": "https://github.com/utopia-php/database/tree/2.2.0" }, - "time": "2025-09-16T13:44:45+00:00" + "time": "2025-09-26T02:23:30+00:00" }, { "name": "utopia-php/detector", @@ -3939,16 +3939,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.27", + "version": "0.33.28", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37" + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/d9d10a895e85c8c7675220347cc6109db9d3bd37", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", "shasum": "" }, "require": { @@ -3980,9 +3980,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.27" + "source": "https://github.com/utopia-php/http/tree/0.33.28" }, - "time": "2025-09-07T18:40:53+00:00" + "time": "2025-09-25T10:44:24+00:00" }, { "name": "utopia-php/image", @@ -6230,16 +6230,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.27", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -6264,7 +6264,7 @@ "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", + "sebastian/exporter": "^4.0.8", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -6313,7 +6313,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -6337,7 +6337,7 @@ "type": "tidelift" } ], - "time": "2025-09-14T06:18:03+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "psr/cache", @@ -6829,16 +6829,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.7", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "eb49b981ef0817890129cb70f774506bebe57740" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/eb49b981ef0817890129cb70f774506bebe57740", - "reference": "eb49b981ef0817890129cb70f774506bebe57740", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -6894,7 +6894,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.7" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { @@ -6914,7 +6914,7 @@ "type": "tidelift" } ], - "time": "2025-09-22T05:18:21+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", From ef147cde21604a76b860d90b3ac50506fc1efef4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 14:28:11 +1200 Subject: [PATCH 214/385] Format --- src/Appwrite/Platform/Workers/StatsUsage.php | 2 +- src/Appwrite/Utopia/Database/Validator/Operation.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index e7e8dba4f3..a2473420a4 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -435,7 +435,7 @@ class StatsUsage extends Action return $cmp; } - unset($this->projects[$sequence]); + unset($this->projects[$sequence]); // Period ASC $cmp = strcmp($a['period'], $b['period']); if ($cmp !== 0) { diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 6bc068a60f..a8de32de79 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -146,7 +146,7 @@ class Operation extends Validator $this->description = "Key 'data' must be an array"; return false; } - } else if (\array_key_exists('data', $value)) { + } elseif (\array_key_exists('data', $value)) { // Data is optional but if provided, must be an array if (!\is_array($value['data'])) { $this->description = "Key 'data' must be an array"; From 372a50f9a573e5410134501dffd609b540c03356 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 14:45:09 +1200 Subject: [PATCH 215/385] Update lock --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 0225722352..d8fb2b1b4a 100644 --- a/composer.lock +++ b/composer.lock @@ -4187,16 +4187,16 @@ }, { "name": "utopia-php/migration", - "version": "1.2.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093" + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/42ff497c5231f5a727d1e229419ff1d2195d8093", - "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/6fb6f8f032cd34c3c65728a55d494adeac2ff038", + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038", "shasum": "" }, "require": { @@ -4237,9 +4237,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.2.0" + "source": "https://github.com/utopia-php/migration/tree/1.1.0" }, - "time": "2025-09-24T10:32:24+00:00" + "time": "2025-09-10T05:45:30+00:00" }, { "name": "utopia-php/orchestration", From a0f0449c21d903badf4dd5b3d62f996a850749b8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 26 Sep 2025 12:33:10 +0530 Subject: [PATCH 216/385] fix: enum typing for platform in specs --- app/config/specs/open-api3-1.8.x-console.json | 16 +++++++++---- .../specs/open-api3-latest-console.json | 16 +++++++++---- app/config/specs/swagger2-1.8.x-console.json | 16 +++++++++---- app/config/specs/swagger2-latest-console.json | 16 +++++++++---- app/controllers/api/projects.php | 23 ++++++++++++++++++- .../Utopia/Response/Model/Platform.php | 4 ++-- 6 files changed, 72 insertions(+), 19 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index ab36ae475c..5eac3ca9f6 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -25153,7 +25153,7 @@ "properties": { "type": { "type": "string", - "description": "Platform type.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", @@ -53440,16 +53440,24 @@ }, "type": { "type": "string", - "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", "flutter-web", "flutter-ios", "flutter-android", - "ios", + "flutter-linux", + "flutter-macos", + "flutter-windows", + "apple-ios", + "apple-macos", + "apple-watchos", + "apple-tvos", "android", - "unity" + "unity", + "react-native-ios", + "react-native-android" ] }, "key": { diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index ab36ae475c..5eac3ca9f6 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -25153,7 +25153,7 @@ "properties": { "type": { "type": "string", - "description": "Platform type.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", @@ -53440,16 +53440,24 @@ }, "type": { "type": "string", - "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", "flutter-web", "flutter-ios", "flutter-android", - "ios", + "flutter-linux", + "flutter-macos", + "flutter-windows", + "apple-ios", + "apple-macos", + "apple-watchos", + "apple-tvos", "android", - "unity" + "unity", + "react-native-ios", + "react-native-android" ] }, "key": { diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index e830f768be..0a6bdf0889 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -25305,7 +25305,7 @@ "properties": { "type": { "type": "string", - "description": "Platform type.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "default": null, "x-example": "web", "enum": [ @@ -53396,16 +53396,24 @@ }, "type": { "type": "string", - "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", "flutter-web", "flutter-ios", "flutter-android", - "ios", + "flutter-linux", + "flutter-macos", + "flutter-windows", + "apple-ios", + "apple-macos", + "apple-watchos", + "apple-tvos", "android", - "unity" + "unity", + "react-native-ios", + "react-native-android" ] }, "key": { diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index e830f768be..0a6bdf0889 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -25305,7 +25305,7 @@ "properties": { "type": { "type": "string", - "description": "Platform type.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "default": null, "x-example": "web", "enum": [ @@ -53396,16 +53396,24 @@ }, "type": { "type": "string", - "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.", + "description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.", "x-example": "web", "enum": [ "web", "flutter-web", "flutter-ios", "flutter-android", - "ios", + "flutter-linux", + "flutter-macos", + "flutter-windows", + "apple-ios", + "apple-macos", + "apple-watchos", + "apple-tvos", "android", - "unity" + "unity", + "react-native-ios", + "react-native-android" ] }, "key": { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ea3a00dcb6..80d407322e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1756,7 +1756,28 @@ App::post('/v1/projects/:projectId/platforms') ] )) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('type', null, new WhiteList([Platform::TYPE_WEB, Platform::TYPE_FLUTTER_WEB, Platform::TYPE_FLUTTER_IOS, Platform::TYPE_FLUTTER_ANDROID, Platform::TYPE_FLUTTER_LINUX, Platform::TYPE_FLUTTER_MACOS, Platform::TYPE_FLUTTER_WINDOWS, Platform::TYPE_APPLE_IOS, Platform::TYPE_APPLE_MACOS, Platform::TYPE_APPLE_WATCHOS, Platform::TYPE_APPLE_TVOS, Platform::TYPE_ANDROID, Platform::TYPE_UNITY, Platform::TYPE_REACT_NATIVE_IOS, Platform::TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.') + ->param( + 'type', + null, + new WhiteList([ + Platform::TYPE_WEB, + Platform::TYPE_FLUTTER_WEB, + Platform::TYPE_FLUTTER_IOS, + Platform::TYPE_FLUTTER_ANDROID, + Platform::TYPE_FLUTTER_LINUX, + Platform::TYPE_FLUTTER_MACOS, + Platform::TYPE_FLUTTER_WINDOWS, + Platform::TYPE_APPLE_IOS, + Platform::TYPE_APPLE_MACOS, + Platform::TYPE_APPLE_WATCHOS, + Platform::TYPE_APPLE_TVOS, + Platform::TYPE_ANDROID, + Platform::TYPE_UNITY, + Platform::TYPE_REACT_NATIVE_IOS, + Platform::TYPE_REACT_NATIVE_ANDROID, + ], true), + 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.' + ) ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.') ->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true) ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) diff --git a/src/Appwrite/Utopia/Response/Model/Platform.php b/src/Appwrite/Utopia/Response/Model/Platform.php index 65f3c343d4..151e43780d 100644 --- a/src/Appwrite/Utopia/Response/Model/Platform.php +++ b/src/Appwrite/Utopia/Response/Model/Platform.php @@ -41,10 +41,10 @@ class Platform extends Model ]) ->addRule('type', [ 'type' => self::TYPE_ENUM, - 'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.', + 'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.', 'default' => '', 'example' => 'web', - 'enum' => ['web', 'flutter-web', 'flutter-ios', 'flutter-android', 'ios', 'android', 'unity'], + 'enum' => ['web', 'flutter-web', 'flutter-ios', 'flutter-android', 'flutter-linux', 'flutter-macos', 'flutter-windows', 'apple-ios', 'apple-macos', 'apple-watchos', 'apple-tvos', 'android', 'unity', 'react-native-ios', 'react-native-android'], ]) ->addRule('key', [ 'type' => self::TYPE_STRING, From a0e8711d9b1ebcaa7b34fd4dc467cf12052228e3 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 26 Sep 2025 15:33:37 +0530 Subject: [PATCH 217/385] chore: update sdks add response models --- app/config/platforms.php | 28 ++++++++++++++-------------- composer.json | 2 +- composer.lock | 18 ++++++++++-------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 8a33144b2f..831f6b9db8 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '20.1.0', + 'version' => '20.2.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '19.1.0', + 'version' => '19.2.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '12.1.0', + 'version' => '12.2.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '10.1.0', + 'version' => '10.2.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -139,7 +139,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.14.0', + 'version' => '0.15.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '19.1.0', + 'version' => '19.2.0', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -281,7 +281,7 @@ return [ [ 'key' => 'php', 'name' => 'PHP', - 'version' => '17.1.0', + 'version' => '17.2.0', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -300,7 +300,7 @@ return [ [ 'key' => 'python', 'name' => 'Python', - 'version' => '13.1.0', + 'version' => '13.2.0', 'url' => 'https://github.com/appwrite/sdk-for-python', 'package' => 'https://pypi.org/project/appwrite/', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '18.1.0', + 'version' => '18.2.0', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => '0.12.0', + 'version' => '0.13.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.18.0', + 'version' => '0.19.0', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '18.1.0', + 'version' => '18.2.0', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '11.1.0', + 'version' => '11.2.0', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '12.1.0', + 'version' => '12.2.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/composer.json b/composer.json index 1dc7288441..626b156f2f 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "*", + "appwrite/sdk-generator": "dev-feat-enum-support", "phpunit/phpunit": "9.*", "swoole/ide-helper": "5.1.2", "phpstan/phpstan": "1.8.*", diff --git a/composer.lock b/composer.lock index f5a8c43b32..71e4d97cec 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": "7553e976312b0423cc31544abb91caec", + "content-hash": "c7b1f0f5834dbeb48c5f9880c217ebef", "packages": [ { "name": "adhocore/jwt", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.0", + "version": "dev-feat-enum-support", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008" + "reference": "80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3583fa6fddb1d1a902b37ff2048527a5827fc008", - "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d", + "reference": "80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.0" + "source": "https://github.com/appwrite/sdk-generator/tree/feat-enum-support" }, - "time": "2025-09-23T02:27:10+00:00" + "time": "2025-09-26T08:58:30+00:00" }, { "name": "doctrine/annotations", @@ -8518,7 +8518,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "appwrite/sdk-generator": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From d5e1f9952ae05efab0b1320395949ea19bf903e2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 29 Sep 2025 11:26:00 +0530 Subject: [PATCH 218/385] chore: add enums for database type and column status --- app/config/specs/open-api3-1.8.x-console.json | 123 ++++++++++++++++-- app/config/specs/open-api3-1.8.x-server.json | 123 ++++++++++++++++-- .../specs/open-api3-latest-console.json | 123 ++++++++++++++++-- app/config/specs/open-api3-latest-server.json | 123 ++++++++++++++++-- app/config/specs/swagger2-1.8.x-console.json | 123 ++++++++++++++++-- app/config/specs/swagger2-1.8.x-server.json | 123 ++++++++++++++++-- app/config/specs/swagger2-latest-console.json | 123 ++++++++++++++++-- app/config/specs/swagger2-latest-server.json | 123 ++++++++++++++++-- src/Appwrite/Utopia/Response/Model/Column.php | 3 +- .../Utopia/Response/Model/Database.php | 3 +- 10 files changed, 876 insertions(+), 114 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 5eac3ca9f6..4a27d5a49b 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -46223,7 +46223,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -47910,7 +47914,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -47998,7 +48009,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48088,7 +48106,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48178,7 +48203,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48251,7 +48283,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48331,7 +48370,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48421,7 +48467,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48501,7 +48554,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48581,7 +48641,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48661,7 +48728,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48769,7 +48843,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48848,7 +48929,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48939,7 +49027,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 93a0d9d46b..50f668ea64 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -35093,7 +35093,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -36780,7 +36784,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36868,7 +36879,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36958,7 +36976,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37048,7 +37073,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37121,7 +37153,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37201,7 +37240,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37291,7 +37337,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37371,7 +37424,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37451,7 +37511,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37531,7 +37598,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37639,7 +37713,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37718,7 +37799,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37809,7 +37897,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 5eac3ca9f6..4a27d5a49b 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -46223,7 +46223,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -47910,7 +47914,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -47998,7 +48009,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48088,7 +48106,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48178,7 +48203,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48251,7 +48283,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48331,7 +48370,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48421,7 +48467,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48501,7 +48554,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48581,7 +48641,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48661,7 +48728,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48769,7 +48843,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48848,7 +48929,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48939,7 +49027,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 93a0d9d46b..50f668ea64 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -35093,7 +35093,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -36780,7 +36784,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36868,7 +36879,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36958,7 +36976,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37048,7 +37073,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37121,7 +37153,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37201,7 +37240,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37291,7 +37337,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37371,7 +37424,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37451,7 +37511,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37531,7 +37598,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37639,7 +37713,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37718,7 +37799,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37809,7 +37897,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 0a6bdf0889..aa0f0ec520 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -46159,7 +46159,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -47848,7 +47852,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -47936,7 +47947,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48026,7 +48044,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48116,7 +48141,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48189,7 +48221,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48269,7 +48308,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48359,7 +48405,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48439,7 +48492,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48519,7 +48579,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48599,7 +48666,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48707,7 +48781,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48786,7 +48867,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48877,7 +48965,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 0a5fc8fe95..b1b10f4543 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -35120,7 +35120,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -36809,7 +36813,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36897,7 +36908,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36987,7 +37005,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37077,7 +37102,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37150,7 +37182,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37230,7 +37269,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37320,7 +37366,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37400,7 +37453,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37480,7 +37540,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37560,7 +37627,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37668,7 +37742,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37747,7 +37828,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37838,7 +37926,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 0a6bdf0889..aa0f0ec520 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -46159,7 +46159,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -47848,7 +47852,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -47936,7 +47947,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48026,7 +48044,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48116,7 +48141,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48189,7 +48221,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48269,7 +48308,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48359,7 +48405,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48439,7 +48492,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48519,7 +48579,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48599,7 +48666,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48707,7 +48781,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48786,7 +48867,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -48877,7 +48965,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 0a5fc8fe95..b1b10f4543 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -35120,7 +35120,11 @@ "type": { "type": "string", "description": "Database type.", - "x-example": "legacy" + "x-example": "legacy", + "enum": [ + "legacy", + "tablesdb" + ] } }, "required": [ @@ -36809,7 +36813,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36897,7 +36908,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -36987,7 +37005,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37077,7 +37102,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37150,7 +37182,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37230,7 +37269,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37320,7 +37366,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37400,7 +37453,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37480,7 +37540,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37560,7 +37627,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37668,7 +37742,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37747,7 +37828,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", @@ -37838,7 +37926,14 @@ "status": { "type": "string", "description": "Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`", - "x-example": "available" + "x-example": "available", + "enum": [ + "available", + "processing", + "deleting", + "stuck", + "failed" + ] }, "error": { "type": "string", diff --git a/src/Appwrite/Utopia/Response/Model/Column.php b/src/Appwrite/Utopia/Response/Model/Column.php index 5562de39f2..cae8d1fadb 100644 --- a/src/Appwrite/Utopia/Response/Model/Column.php +++ b/src/Appwrite/Utopia/Response/Model/Column.php @@ -23,10 +23,11 @@ class Column extends Model 'example' => 'string', ]) ->addRule('status', [ - 'type' => self::TYPE_STRING, + 'type' => self::TYPE_ENUM, 'description' => 'Column status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`', 'default' => '', 'example' => 'available', + 'enum' => ['available', 'processing', 'deleting', 'stuck', 'failed'], ]) ->addRule('error', [ 'type' => self::TYPE_STRING, diff --git a/src/Appwrite/Utopia/Response/Model/Database.php b/src/Appwrite/Utopia/Response/Model/Database.php index 44a0d52af8..59f32b3162 100644 --- a/src/Appwrite/Utopia/Response/Model/Database.php +++ b/src/Appwrite/Utopia/Response/Model/Database.php @@ -41,10 +41,11 @@ class Database extends Model 'example' => false, ]) ->addRule('type', [ - 'type' => self::TYPE_STRING, + 'type' => self::TYPE_ENUM, 'description' => 'Database type.', 'default' => 'legacy', 'example' => 'legacy', + 'enum' => ['legacy', 'tablesdb'], ]) ; } From 5245ff7167dcbbe72e798a4a1eb819eaf460afcf Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 29 Sep 2025 11:29:40 +0530 Subject: [PATCH 219/385] update enum name --- app/config/specs/open-api3-1.8.x-console.json | 39 ++++++---- app/config/specs/open-api3-1.8.x-server.json | 39 ++++++---- .../specs/open-api3-latest-console.json | 39 ++++++---- app/config/specs/open-api3-latest-server.json | 39 ++++++---- app/config/specs/swagger2-1.8.x-console.json | 39 ++++++---- app/config/specs/swagger2-1.8.x-server.json | 39 ++++++---- app/config/specs/swagger2-latest-console.json | 39 ++++++---- app/config/specs/swagger2-latest-server.json | 39 ++++++---- src/Appwrite/SDK/Specification/Format.php | 78 +++++++++++++++++++ 9 files changed, 286 insertions(+), 104 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 4a27d5a49b..bdff664cbc 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -47921,7 +47921,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48016,7 +48017,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48113,7 +48115,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48210,7 +48213,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48290,7 +48294,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48377,7 +48382,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48474,7 +48480,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48561,7 +48568,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48648,7 +48656,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48735,7 +48744,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48850,7 +48860,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48936,7 +48947,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -49034,7 +49046,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 50f668ea64..6b766dbdee 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -36791,7 +36791,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36886,7 +36887,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36983,7 +36985,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37080,7 +37083,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37160,7 +37164,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37247,7 +37252,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37344,7 +37350,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37431,7 +37438,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37518,7 +37526,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37605,7 +37614,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37720,7 +37730,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37806,7 +37817,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37904,7 +37916,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 4a27d5a49b..bdff664cbc 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -47921,7 +47921,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48016,7 +48017,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48113,7 +48115,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48210,7 +48213,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48290,7 +48294,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48377,7 +48382,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48474,7 +48480,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48561,7 +48568,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48648,7 +48656,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48735,7 +48744,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48850,7 +48860,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48936,7 +48947,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -49034,7 +49046,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 50f668ea64..6b766dbdee 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -36791,7 +36791,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36886,7 +36887,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36983,7 +36985,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37080,7 +37083,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37160,7 +37164,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37247,7 +37252,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37344,7 +37350,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37431,7 +37438,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37518,7 +37526,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37605,7 +37614,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37720,7 +37730,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37806,7 +37817,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37904,7 +37916,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index aa0f0ec520..ee3702d27d 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -47859,7 +47859,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -47954,7 +47955,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48051,7 +48053,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48148,7 +48151,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48228,7 +48232,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48315,7 +48320,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48412,7 +48418,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48499,7 +48506,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48586,7 +48594,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48673,7 +48682,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48788,7 +48798,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48874,7 +48885,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48972,7 +48984,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index b1b10f4543..ff5056b35a 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -36820,7 +36820,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36915,7 +36916,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37012,7 +37014,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37109,7 +37112,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37189,7 +37193,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37276,7 +37281,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37373,7 +37379,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37460,7 +37467,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37547,7 +37555,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37634,7 +37643,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37749,7 +37759,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37835,7 +37846,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37933,7 +37945,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index aa0f0ec520..ee3702d27d 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -47859,7 +47859,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -47954,7 +47955,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48051,7 +48053,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48148,7 +48151,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48228,7 +48232,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48315,7 +48320,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48412,7 +48418,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48499,7 +48506,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48586,7 +48594,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48673,7 +48682,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48788,7 +48798,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48874,7 +48885,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -48972,7 +48984,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index b1b10f4543..ff5056b35a 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -36820,7 +36820,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -36915,7 +36916,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37012,7 +37014,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37109,7 +37112,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37189,7 +37193,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37276,7 +37281,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37373,7 +37379,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37460,7 +37467,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37547,7 +37555,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37634,7 +37643,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37749,7 +37759,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37835,7 +37846,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", @@ -37933,7 +37945,8 @@ "deleting", "stuck", "failed" - ] + ], + "x-enum-name": "ColumnStatus" }, "error": { "type": "string", diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index db7335e40f..c687df143a 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -624,6 +624,84 @@ abstract class Format return 'AttributeStatus'; } break; + case 'columnString': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnInteger': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnFloat': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnBoolean': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnEmail': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnEnum': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnIp': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnUrl': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnDatetime': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnRelationship': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnPoint': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnLine': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; + case 'columnPolygon': + switch ($param) { + case 'status': + return 'ColumnStatus'; + } + break; case 'healthStatus': switch ($param) { case 'status': From b614045d97fd99b0ddee5e08b672f3f91862cf9f Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 30 Sep 2025 18:08:49 +0400 Subject: [PATCH 220/385] fix(builds-worker): Use outputDirectory attribute from deployment or resource --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index ce45d6b629..9fcf64814c 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -692,9 +692,11 @@ class Builds extends Action // Start separation, enter build folder $listFilesCommand .= 'echo "{APPWRITE_DETECTION_SEPARATOR_START}" && cd /usr/local/build'; + $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); + // Enter output directory, if set - if (!empty($resource->getAttribute('outputDirectory', ''))) { - $listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory', '')); + if ($outputDirectory !== null) { + $listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory')); } // Print files, and end separation From 04d66938037f03c5c695703c3b5679bc66c8b28e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 30 Sep 2025 18:10:53 +0400 Subject: [PATCH 221/385] Fix output directory path validation and position --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9fcf64814c..f4b5056bb2 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -692,10 +692,9 @@ class Builds extends Action // Start separation, enter build folder $listFilesCommand .= 'echo "{APPWRITE_DETECTION_SEPARATOR_START}" && cd /usr/local/build'; - $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); - // Enter output directory, if set - if ($outputDirectory !== null) { + $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); + if (!empty($outputDirectory)) { $listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory')); } From 4b985441123a19e61d5a0926a7f06edcf9fc1258 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 30 Sep 2025 18:31:37 +0400 Subject: [PATCH 222/385] Move outputDirectory initialization before conditional flow --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index f4b5056bb2..fcbaf1acb1 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -686,6 +686,7 @@ class Builds extends Action if ($version === 'v2') { $command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh'; } else { + $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); if ($resource->getCollection() === 'sites') { $listFilesCommand = ''; @@ -693,7 +694,6 @@ class Builds extends Action $listFilesCommand .= 'echo "{APPWRITE_DETECTION_SEPARATOR_START}" && cd /usr/local/build'; // Enter output directory, if set - $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); if (!empty($outputDirectory)) { $listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory')); } @@ -726,7 +726,7 @@ class Builds extends Action destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}", variables: $vars, command: $command, - outputDirectory: $resource->getAttribute('outputDirectory', '') + outputDirectory: $outputDirectory ?? '' ); Console::log('createRuntime finished'); From debdfeedf1bd35b8797384d2e6e9b22472050493 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 30 Sep 2025 18:33:22 +0400 Subject: [PATCH 223/385] Fix variable reference in function build command --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index fcbaf1acb1..e042099a97 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -695,7 +695,7 @@ class Builds extends Action // Enter output directory, if set if (!empty($outputDirectory)) { - $listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory')); + $listFilesCommand .= ' && cd ' . \escapeshellarg($outputDirectory); } // Print files, and end separation From b7604a5742d63863fc0ec00255d26b02eb858614 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 30 Sep 2025 19:51:17 +0400 Subject: [PATCH 224/385] Fix buildOutput attribute name in deployment check --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index e042099a97..d6385c1f40 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -686,7 +686,7 @@ class Builds extends Action if ($version === 'v2') { $command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh'; } else { - $outputDirectory = $deployment->getAttribute('outputDirectory') ?? $resource->getAttribute('outputDirectory'); + $outputDirectory = $deployment->getAttribute('buildOutput') ?? $resource->getAttribute('outputDirectory'); if ($resource->getCollection() === 'sites') { $listFilesCommand = ''; From 3dd099bd9476cd46f86dc3b85255a884fed1603c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 1 Oct 2025 17:05:12 +1300 Subject: [PATCH 225/385] Update database for nested selection fix --- composer.lock | 73 +++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/composer.lock b/composer.lock index f5a8c43b32..ab8887de49 100644 --- a/composer.lock +++ b/composer.lock @@ -2596,16 +2596,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { @@ -2672,7 +2672,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.3" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -2692,7 +2692,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T07:45:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "24c4519b4ac32aee13af31dddd984db2a3b34980" + "reference": "7ec0238bf0197a38c66f392c65355d5e7eed8248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/24c4519b4ac32aee13af31dddd984db2a3b34980", - "reference": "24c4519b4ac32aee13af31dddd984db2a3b34980", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7ec0238bf0197a38c66f392c65355d5e7eed8248", + "reference": "7ec0238bf0197a38c66f392c65355d5e7eed8248", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.5.0" + "source": "https://github.com/utopia-php/database/tree/1.5.1" }, - "time": "2025-09-18T14:42:01+00:00" + "time": "2025-10-01T03:51:58+00:00" }, { "name": "utopia-php/detector", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.0", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008" + "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3583fa6fddb1d1a902b37ff2048527a5827fc008", - "reference": "3583fa6fddb1d1a902b37ff2048527a5827fc008", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07a7d6276bd684b49469ad7b9e8c3c962121c6fd", + "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.0" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.2" }, - "time": "2025-09-23T02:27:10+00:00" + "time": "2025-10-01T03:23:04+00:00" }, { "name": "doctrine/annotations", @@ -7497,16 +7497,16 @@ }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -7571,7 +7571,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -7591,7 +7591,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/filesystem", @@ -8134,16 +8134,16 @@ }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -8175,7 +8175,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -8195,20 +8195,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:42:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -8223,7 +8223,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -8266,7 +8265,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -8286,7 +8285,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "textalk/websocket", From 397c41e68bd90005d03c347da72d7470d52c5988 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 1 Oct 2025 09:46:20 +0530 Subject: [PATCH 226/385] update cli --- app/config/platforms.php | 2 +- composer.json | 2 +- composer.lock | 67 ++++++++++++++++++-------------------- docs/sdks/cli/CHANGELOG.md | 7 ++++ 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 831f6b9db8..5b42381da3 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -226,7 +226,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '10.0.0', + 'version' => '10.0.1', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, diff --git a/composer.json b/composer.json index 626b156f2f..1dc7288441 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "dev-feat-enum-support", + "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 71e4d97cec..61f1dc2355 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": "c7b1f0f5834dbeb48c5f9880c217ebef", + "content-hash": "7553e976312b0423cc31544abb91caec", "packages": [ { "name": "adhocore/jwt", @@ -2596,16 +2596,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { @@ -2672,7 +2672,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.3" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -2692,7 +2692,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T07:45:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "dev-feat-enum-support", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d" + "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d", - "reference": "80b76ff4d97e7c5ca27abc540cc5f7ca8d56ee4d", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07a7d6276bd684b49469ad7b9e8c3c962121c6fd", + "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/feat-enum-support" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.2" }, - "time": "2025-09-26T08:58:30+00:00" + "time": "2025-10-01T03:23:04+00:00" }, { "name": "doctrine/annotations", @@ -7497,16 +7497,16 @@ }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -7571,7 +7571,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -7591,7 +7591,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/filesystem", @@ -8134,16 +8134,16 @@ }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -8175,7 +8175,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -8195,20 +8195,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:42:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -8223,7 +8223,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -8266,7 +8265,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -8286,7 +8285,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "textalk/websocket", @@ -8518,9 +8517,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "appwrite/sdk-generator": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index 000a7e0938..5af193d491 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 10.0.1 + +* Fix CLI Dart model generation issues +* Fix row permissions and security sync +* Fix error when pushing columns with relationships +* Fix resource name from attributes to columns for TablesDB indexes + ## 10.0.0 * **Breaking:** Removed Avatars CLI command and all related subcommands; corresponding examples deleted From 3d43f851451a27fcb716378951e87d2d25f4006c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 1 Oct 2025 10:06:55 +0530 Subject: [PATCH 227/385] add temp hack --- src/Appwrite/Platform/Tasks/SDKs.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index e5c18338bf..90aa07f726 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -298,6 +298,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $repoBranch = $language['repoBranch'] ?? 'main'; if ($git && !empty($gitUrl)) { + // TODO: fix the temporary 2>/dev/null || true - added due to permission issues when removing files \exec('rm -rf ' . $target . ' && \ mkdir -p ' . $target . ' && \ cd ' . $target . ' && \ @@ -309,7 +310,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND git checkout ' . $gitBranch . ' || git checkout -b ' . $gitBranch . ' && \ git fetch origin ' . $gitBranch . ' || git push -u origin ' . $gitBranch . ' && \ git pull origin ' . $gitBranch . ' && \ - rm -rf ' . $target . '/* && \ + rm -rf ' . $target . '/* 2>/dev/null || true && \ cp -r ' . $result . '/. ' . $target . '/ && \ git add . && \ git commit -m "' . $message . '" && \ From b1757250d1960c3dc3f659808edccc62bb5285ab Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 1 Oct 2025 17:47:42 +1300 Subject: [PATCH 228/385] Fix update --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index ab8887de49..95288fc01d 100644 --- a/composer.lock +++ b/composer.lock @@ -3639,12 +3639,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7ec0238bf0197a38c66f392c65355d5e7eed8248" + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7ec0238bf0197a38c66f392c65355d5e7eed8248", - "reference": "7ec0238bf0197a38c66f392c65355d5e7eed8248", + "url": "https://api.github.com/repos/utopia-php/database/zipball/56efe4daaf23abb753553acffccdcc04cd6178c9", + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9", "shasum": "" }, "require": { @@ -3687,7 +3687,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/1.5.1" }, - "time": "2025-10-01T03:51:58+00:00" + "time": "2025-10-01T04:44:14+00:00" }, { "name": "utopia-php/detector", From b4dd1df0c745b52fa40a2435c8c4cbe6f36084e5 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 1 Oct 2025 11:56:48 +0530 Subject: [PATCH 229/385] update composer --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 61f1dc2355..95288fc01d 100644 --- a/composer.lock +++ b/composer.lock @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "24c4519b4ac32aee13af31dddd984db2a3b34980" + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/24c4519b4ac32aee13af31dddd984db2a3b34980", - "reference": "24c4519b4ac32aee13af31dddd984db2a3b34980", + "url": "https://api.github.com/repos/utopia-php/database/zipball/56efe4daaf23abb753553acffccdcc04cd6178c9", + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.5.0" + "source": "https://github.com/utopia-php/database/tree/1.5.1" }, - "time": "2025-09-18T14:42:01+00:00" + "time": "2025-10-01T04:44:14+00:00" }, { "name": "utopia-php/detector", From 558904e103cfe68cbf6ad1394559bedf9393387b Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal <114170980+Priyanshuthapliyal2005@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:58:22 +0000 Subject: [PATCH 230/385] fix(listTemplates): fix invalid template links in Create temporary deployment endpoint --- app/config/specs/open-api3-1.8.x-console.json | 8 ++++---- app/config/specs/open-api3-1.8.x-server.json | 8 ++++---- app/config/specs/open-api3-latest-console.json | 8 ++++---- app/config/specs/open-api3-latest-server.json | 8 ++++---- app/config/specs/swagger2-1.8.x-console.json | 8 ++++---- app/config/specs/swagger2-1.8.x-server.json | 8 ++++---- app/config/specs/swagger2-latest-console.json | 8 ++++---- app/config/specs/swagger2-latest-server.json | 8 ++++---- .../Functions/Http/Deployments/Template/Create.php | 2 +- .../Modules/Sites/Http/Deployments/Template/Create.php | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index bdff664cbc..c58e0b87e9 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -12854,7 +12854,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -12875,7 +12875,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -30132,7 +30132,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -30153,7 +30153,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 6b766dbdee..786c103ba8 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -11635,7 +11635,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -11656,7 +11656,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -20838,7 +20838,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -20859,7 +20859,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index bdff664cbc..c58e0b87e9 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -12854,7 +12854,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -12875,7 +12875,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -30132,7 +30132,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -30153,7 +30153,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 6b766dbdee..786c103ba8 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -11635,7 +11635,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -11656,7 +11656,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -20838,7 +20838,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -20859,7 +20859,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index ee3702d27d..4c3abc01d2 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -12856,7 +12856,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -12873,7 +12873,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -30314,7 +30314,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -30331,7 +30331,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index ff5056b35a..ce45d12606 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -11662,7 +11662,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -11679,7 +11679,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -21063,7 +21063,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -21080,7 +21080,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index ee3702d27d..4c3abc01d2 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -12856,7 +12856,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -12873,7 +12873,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -30314,7 +30314,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -30331,7 +30331,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index ff5056b35a..ce45d12606 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -11662,7 +11662,7 @@ "tags": [ "functions" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -11679,7 +11679,7 @@ "cookies": false, "type": "", "demo": "functions\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/functions\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", @@ -21063,7 +21063,7 @@ "tags": [ "sites" ], - "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "description": "Create a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "responses": { "202": { "description": "Deployment", @@ -21080,7 +21080,7 @@ "cookies": false, "type": "", "demo": "sites\/create-template-deployment.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/products\/sites\/templates) to find the template details.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php index 4d93c8e8cd..bbe84c56ee 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php @@ -51,7 +51,7 @@ class Create extends Base description: << Date: Wed, 1 Oct 2025 18:17:35 +0530 Subject: [PATCH 231/385] fix: test file --- .../functions/log-error-truncation/index.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/resources/functions/log-error-truncation/index.js b/tests/resources/functions/log-error-truncation/index.js index 7e94fa5028..35747095f5 100644 --- a/tests/resources/functions/log-error-truncation/index.js +++ b/tests/resources/functions/log-error-truncation/index.js @@ -2,8 +2,21 @@ module.exports = async(context) => { // Create a string that is 1000001 characters long (exceeds the 1000000 limit) const longString = 'z' + 'a'.repeat(1000000); - context.log(longString); - context.error(longString); + // Split the string into chunks of 8000 characters (max limit for each log and error) + const chunkSize = 8000; + const chunks = []; + + for (let i = 0; i < longString.length; i += chunkSize) { + chunks.push(longString.slice(i, i + chunkSize)); + } + + chunks.forEach((chunk, index) => { + context.log(chunk); + }); + + chunks.forEach((chunk, index) => { + context.error(chunk); + }); return context.res.json({ motto: 'Build like a team of hundreds_', From 813bc0d9b331df653c3a9f5d41fc4bb465997909 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 1 Oct 2025 18:25:32 +0530 Subject: [PATCH 232/385] fix: wrong audit resource. --- .../Http/Databases/Collections/Documents/Logs/XList.php | 5 +---- .../Databases/Http/Databases/Collections/Logs/XList.php | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php index 93c8639520..ed9378f644 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php @@ -108,10 +108,7 @@ class XList extends Action $audit = new Audit($dbForProject); $type = $this->getCollectionsEventsContext(); $context = $this->getContext(); - $resource = match ($context) { - ROWS => "database/$databaseId/grid/$type/$collectionId/$context/{$document->getId()}", - default => "database/$databaseId/$type/$collectionId/$context/{$document->getId()}", - }; + $resource = "database/$databaseId/$type/$collectionId/$context/{$document->getId()}"; $logs = $audit->getLogsByResource($resource, $queries); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php index 1309793234..fefc5ba5a0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php @@ -104,11 +104,7 @@ class XList extends Action $audit = new Audit($dbForProject); $context = $this->getContext(); - $resource = match ($context) { - TABLES => "database/$databaseId/grid/$context/$collectionId", - default => "database/$databaseId/$context/$collectionId", - }; - + $resource = "database/$databaseId/$context/$collectionId"; $logs = $audit->getLogsByResource($resource, $queries); $output = []; From 26ae33522df1159d9d1b6ab4135d6fbadf7f3bd0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 21:36:33 +1300 Subject: [PATCH 233/385] Fix txn read scope --- .../Modules/Databases/Http/Databases/Transactions/Get.php | 2 +- .../Modules/Databases/Http/Databases/Transactions/XList.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php index 43406a6751..1d4d22baa1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Get.php @@ -31,7 +31,7 @@ class Get extends Action ->setHttpPath('/v1/databases/transactions/:transactionId') ->desc('Get transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.read') + ->label('scope', 'rows.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php index 05ef5ae9e8..33c66b90c7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/XList.php @@ -34,7 +34,7 @@ class XList extends Action ->setHttpPath('/v1/databases/transactions') ->desc('List transactions') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'transactions.read') + ->label('scope', 'rows.read') ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'databases', From ae661e996157e98230a0bb9d2508cf4e2afb9057 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 21:37:54 +1300 Subject: [PATCH 234/385] Fix missing bulk map --- .../Modules/Databases/Http/Databases/Transactions/Update.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 9f5e5037bf..214052a173 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -545,7 +545,7 @@ class Update extends Action $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { $dbForProject->createDocuments( $collectionId, - $data, + \array_map(fn($doc) => new Document($doc), $data), onNext: function (Document $document) use (&$state, $collectionId) { $state[$collectionId][$document->getId()] = $document; } @@ -603,7 +603,7 @@ class Update extends Action // Run bulk upsert without timestamp wrapper, checking manually in callback $dbForProject->upsertDocuments( $collectionId, - $data, + \array_map(fn($doc) => new Document($doc), $data), onNext: function (Document $upserted, ?Document $old) use (&$state, $collectionId, $createdAt) { if ($old !== null) { // This is an update - check if document was created/modified in this transaction From 490341af090b1672fb7fa7c7fcdbfaf3c499d33e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 21:46:28 +1300 Subject: [PATCH 235/385] Fix log limit --- .../Modules/Databases/Http/Databases/Transactions/Update.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 214052a173..ac2efc31cf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -133,6 +133,7 @@ class Update extends Action // Fetch operations ordered by sequence by default to replay operations in exact order they were created $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::limit(PHP_INT_MAX), ]); // Track transaction state for cross-operation visibility From f667149556f1530e0f01f56f2a52b25b657e1bc2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 22:12:02 +1300 Subject: [PATCH 236/385] Trigger CI From 473be9284d8c3fb0767fd0df8ced1681a12906bd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 22:24:54 +1300 Subject: [PATCH 237/385] Check convert --- .../Http/Databases/Transactions/Update.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index ac2efc31cf..afa466c366 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -544,9 +544,14 @@ class Update extends Action array &$state ): void { $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { + // Convert data arrays to Document objects if needed + $documents = \array_map(function ($doc) { + return $doc instanceof Document ? $doc : new Document($doc); + }, $data); + $dbForProject->createDocuments( $collectionId, - \array_map(fn($doc) => new Document($doc), $data), + $documents, onNext: function (Document $document) use (&$state, $collectionId) { $state[$collectionId][$document->getId()] = $document; } @@ -601,10 +606,15 @@ class Update extends Action \DateTime $createdAt, array &$state ): void { + // Convert data arrays to Document objects if needed + $documents = \array_map(function ($doc) { + return $doc instanceof Document ? $doc : new Document($doc); + }, $data); + // Run bulk upsert without timestamp wrapper, checking manually in callback $dbForProject->upsertDocuments( $collectionId, - \array_map(fn($doc) => new Document($doc), $data), + $documents, onNext: function (Document $upserted, ?Document $old) use (&$state, $collectionId, $createdAt) { if ($old !== null) { // This is an update - check if document was created/modified in this transaction From ca1c069e65d38a269111cf8f29504a8fd1a61bf4 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 2 Oct 2025 14:58:55 +0530 Subject: [PATCH 238/385] Handle OIDC well-known endpoint errors --- src/Appwrite/Auth/OAuth2/Oidc.php | 3 + .../Account/AccountCustomClientTest.php | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/Appwrite/Auth/OAuth2/Oidc.php b/src/Appwrite/Auth/OAuth2/Oidc.php index 670169fe89..c9810c48eb 100644 --- a/src/Appwrite/Auth/OAuth2/Oidc.php +++ b/src/Appwrite/Auth/OAuth2/Oidc.php @@ -273,6 +273,9 @@ class Oidc extends OAuth2 { if (empty($this->wellKnownConfiguration)) { $response = $this->request('GET', $this->getWellKnownEndpoint()); + if (empty($response)) { + throw new Exception('Invalid well-known configuration'); + } $this->wellKnownConfiguration = \json_decode($response, true); } diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index bd3fec8439..8322508cf6 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1539,6 +1539,77 @@ class AccountCustomClientTest extends Scope return []; } + public function testCreateOidcOAuth2Token(): array + { + $provider = 'oidc'; + $appId = '1'; + + // Valid well-known configuration + $secret = '{ + "wellKnownEndpoint": "https://accounts.google.com/.well-known/openid-configuration", + "authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenEndpoint": "https://oauth2.googleapis.com/token", + "userinfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo" + }'; + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'provider' => $provider, + 'appId' => $appId, + 'secret' => $secret, + 'enabled' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'provider' => $provider, + 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', + 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', + ], true, false); + + $this->assertEquals(301, $response['headers']['status-code']); + + // Invalid well-known configuration + $secret = '{}'; + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'provider' => $provider, + 'appId' => $appId, + 'secret' => $secret, + 'enabled' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'provider' => $provider, + 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', + 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', + ]); + + $this->assertEquals(500, $response['headers']['status-code']); + + return []; + } + public function testBlockedAccount(): array { $email = uniqid() . 'user@localhost.test'; From 93d8f1cfe7f3eb66fc6c05b3236421090231978e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:03:52 +1300 Subject: [PATCH 239/385] Update src/Appwrite/Databases/TransactionState.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/Appwrite/Databases/TransactionState.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 1caae8c629..495a7b70ef 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -49,15 +49,15 @@ class TransactionState switch ($action) { case 'create': - if ($documentId) { - $state[$collectionId][$documentId] = [ + $docId = $documentId ?? ($data['$id'] ?? null); + if ($docId) { + $state[$collectionId][$docId] = [ 'action' => 'create', 'document' => new Document($data), 'exists' => true ]; } break; - case 'update': if (isset($state[$collectionId][$documentId])) { // Update existing document in transaction state From 9813b98133bc0682c195a8c6bc5f64329cfb056d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:05:43 +1300 Subject: [PATCH 240/385] Update src/Appwrite/Databases/TransactionState.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/Appwrite/Databases/TransactionState.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 495a7b70ef..c20eac3d1a 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -79,7 +79,9 @@ class TransactionState break; case 'upsert': - $state[$collectionId][$documentId] = [ + $docId = $documentId ?? ($data['$id'] ?? null); + if (!$docId) { break; } + $state[$collectionId][$docId] = [ 'action' => 'upsert', 'document' => new Document($data), 'exists' => true From 51d5ddb4637d0de70e21eb2ccc96842f40ad0415 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:17:17 +1300 Subject: [PATCH 241/385] Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 6123454a8b..e5ae97ba7c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -124,7 +124,7 @@ class Upsert extends Action if (($existing + 1) > $maxBatch) { throw new Exception( Exception::TRANSACTION_LIMIT_EXCEEDED, - 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch ); } From 6581d68e6d9daf40b7cbf44f065fdb64862ac477 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:20:02 +1300 Subject: [PATCH 242/385] Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index e5ae97ba7c..2c8d26020d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -144,7 +144,7 @@ class Upsert extends Action 'transactions', $transactionId, 'operations', - \count($staged) + 1 ); }); From a9a97b2fe0b735fa006d7f3d9bbd346be41f0709 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:20:22 +1300 Subject: [PATCH 243/385] Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Databases/Http/Databases/Collections/Documents/Create.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 67a54068b8..902a3585ba 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 @@ -381,7 +381,7 @@ class Create extends Action if (($existing + 1) > $maxBatch) { throw new Exception( Exception::TRANSACTION_LIMIT_EXCEEDED, - 'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch + 'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch ); } From 45da32d5d9129c8b0815d2ec0b7a82d2af9e82e2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Oct 2025 23:57:30 +1300 Subject: [PATCH 244/385] Update src/Appwrite/Utopia/Database/Validator/Operation.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Utopia/Database/Validator/Operation.php | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index a8de32de79..5cc3ca2135 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -127,14 +127,25 @@ class Operation extends Validator } // If action requires documentId, it must be present - if ( - isset($this->requiresDocumentId[$value['action']]) && - !\array_key_exists($this->documentIdName, $value) - ) { +- if ( +- isset($this->requiresDocumentId[$value['action']]) && +- !\array_key_exists($this->documentIdName, $value) +- ) { +- $this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'"; +- return false; + $actionRequiresDocumentId = ($this->requiresDocumentId[$value['action']] ?? false) === true; + if ($actionRequiresDocumentId && !\array_key_exists($this->documentIdName, $value)) { $this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'"; return false; } + if (\array_key_exists($this->documentIdName, $value)) { + if (!\is_string($value[$this->documentIdName]) || \trim($value[$this->documentIdName]) === '') { + $this->description = "Key '$this->documentIdName' must be a non-empty string"; + return false; + } + } + // Data validation - only required for certain actions if (isset($this->requiresData[$value['action']]) && $this->requiresData[$value['action']]) { // Data is required for this action @@ -146,7 +157,7 @@ class Operation extends Validator $this->description = "Key 'data' must be an array"; return false; } - } elseif (\array_key_exists('data', $value)) { + } else if (\array_key_exists('data', $value)) { // Data is optional but if provided, must be an array if (!\is_array($value['data'])) { $this->description = "Key 'data' must be an array"; From 36a8365ebbea6457cb53ea57d18af34599244930 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:03:20 +1300 Subject: [PATCH 245/385] Count from state --- src/Appwrite/Databases/TransactionState.php | 265 +++++++++++++++++- .../Databases/Collections/Documents/XList.php | 2 +- 2 files changed, 258 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 1caae8c629..6d51356f89 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -18,6 +18,56 @@ class TransactionState $this->dbForProject = $dbForProject; } + /** + * Apply projection (select) semantics from queries to a document + */ + private function applyProjection(Document $doc, array $queries): Document + { + if (empty($queries)) { + return $doc; + } + + // Extract selections from queries + $selections = []; + foreach ($queries as $query) { + if ($query->getMethod() === Query::TYPE_SELECT) { + $values = $query->getValues(); + foreach ($values as $value) { + // Skip relationship selections (containing '.') + if (!\str_contains($value, '.')) { + $selections[] = $value; + } + } + } + } + + // If no selections or wildcard present, return document as-is + if (empty($selections) || \in_array('*', $selections)) { + return $doc; + } + + // Create a new document with only selected attributes + $projected = new Document(); + + // Always preserve internal attributes + $projected->setAttribute('$id', $doc->getId()); + $projected->setAttribute('$collection', $doc->getCollection()); + $projected->setAttribute('$createdAt', $doc->getCreatedAt()); + $projected->setAttribute('$updatedAt', $doc->getUpdatedAt()); + if ($doc->offsetExists('$permissions')) { + $projected->setAttribute('$permissions', $doc->getPermissions()); + } + + // Add selected attributes + foreach ($selections as $attribute) { + if ($doc->offsetExists($attribute)) { + $projected->setAttribute($attribute, $doc->getAttribute($attribute)); + } + } + + return $projected; + } + /** * Get the current state of a transaction by replaying its operations */ @@ -67,7 +117,11 @@ class TransactionState $existingDocument->setAttribute($key, $value); } } - $state[$collectionId][$documentId]['action'] = 'update'; + // Only set action to 'update' if it's not already 'create' or 'upsert' + $currentAction = $state[$collectionId][$documentId]['action']; + if ($currentAction !== 'create' && $currentAction !== 'upsert') { + $state[$collectionId][$documentId]['action'] = 'update'; + } } else { // Document doesn't exist in transaction state, will be merged with committed version $state[$collectionId][$documentId] = [ @@ -141,7 +195,7 @@ class TransactionState if ($docState['action'] === 'create') { // Document was created in transaction, return the created version - return $docState['document']; + return $this->applyProjection($docState['document'], $queries); } if ($docState['action'] === 'update' || $docState['action'] === 'upsert') { @@ -154,10 +208,12 @@ class TransactionState $committedDoc->setAttribute($key, $value); } } - return $committedDoc; + // committedDoc already has projection applied by dbForProject->getDocument() + // But we need to reapply in case transaction added new fields + return $this->applyProjection($committedDoc, $queries); } elseif ($docState['action'] === 'upsert') { // Upsert created a new document since committed doc doesn't exist - return $docState['document']; + return $this->applyProjection($docState['document'], $queries); } } } @@ -195,8 +251,8 @@ class TransactionState // Document was deleted, remove from results unset($documentMap[$docId]); } elseif ($docState['action'] === 'create') { - // Document was created, add to results - $documentMap[$docId] = $docState['document']; + // Document was created, add to results with projection + $documentMap[$docId] = $this->applyProjection($docState['document'], $queries); } elseif ($docState['action'] === 'update' || $docState['action'] === 'upsert') { if (isset($documentMap[$docId])) { // Update existing document @@ -205,9 +261,11 @@ class TransactionState $documentMap[$docId]->setAttribute($key, $value); } } + // Reapply projection in case transaction added new fields + $documentMap[$docId] = $this->applyProjection($documentMap[$docId], $queries); } elseif ($docState['action'] === 'upsert') { - // Upsert created a new document - $documentMap[$docId] = $docState['document']; + // Upsert created a new document, apply projection + $documentMap[$docId] = $this->applyProjection($docState['document'], $queries); } } } @@ -216,6 +274,197 @@ class TransactionState return array_values($documentMap); } + /** + * Count documents with transaction-aware logic + */ + public function countDocuments( + string $collectionId, + ?string $transactionId = null, + array $queries = [] + ): int { + // If no transaction, use normal database count + if ($transactionId === null) { + return $this->dbForProject->count($collectionId, $queries, APP_LIMIT_COUNT); + } + + $state = $this->getTransactionState($transactionId); + + // Get base count from database + $baseCount = $this->dbForProject->count($collectionId, $queries, APP_LIMIT_COUNT); + + // If no transaction state for this collection, return base count + if (!isset($state[$collectionId])) { + return $baseCount; + } + + // Build a set of committed document IDs that match the query + // We need to find which documents match the filters + $committedDocs = $this->dbForProject->find($collectionId, $queries); + $committedDocIds = []; + foreach ($committedDocs as $doc) { + $committedDocIds[$doc->getId()] = true; + } + + $adjustedCount = $baseCount; + + // Apply transaction state changes to the count + foreach ($state[$collectionId] as $docId => $docState) { + if (!$docState['exists']) { + // Document was deleted in transaction + if (isset($committedDocIds[$docId])) { + $adjustedCount--; // Was in results, now deleted + } + } elseif ($docState['action'] === 'create') { + // Document was created in transaction + // We need to check if it would match the query filters + // For now, we'll conservatively add it if no filters are present + // or apply basic filter matching + if ($this->documentMatchesFilters($docState['document'], $queries)) { + $adjustedCount++; // New document that matches + } + } elseif ($docState['action'] === 'update' || $docState['action'] === 'upsert') { + // Document was updated/upserted + $wasInResults = isset($committedDocIds[$docId]); + $nowMatches = $this->documentMatchesFilters($docState['document'], $queries); + + if (!$wasInResults && $nowMatches && $docState['action'] === 'upsert') { + $adjustedCount++; // Upsert created new document that matches + } elseif ($wasInResults && !$nowMatches) { + $adjustedCount--; // Update/upsert made document no longer match + } elseif (!$wasInResults && $nowMatches) { + // Update shouldn't add a new doc, but upsert might have + if ($docState['action'] === 'upsert') { + $adjustedCount++; + } + } + } + } + + return max(0, $adjustedCount); + } + + /** + * Check if a document matches filter queries (simplified implementation) + */ + private function documentMatchesFilters(Document $doc, array $queries): bool + { + // Extract filter queries + $filters = []; + foreach ($queries as $query) { + $method = $query->getMethod(); + // Only process filter queries, not limit/offset/cursor/select + if (!\in_array($method, [ + Query::TYPE_LIMIT, + Query::TYPE_OFFSET, + Query::TYPE_CURSOR_AFTER, + Query::TYPE_CURSOR_BEFORE, + Query::TYPE_SELECT, + Query::TYPE_ORDER_ASC, + Query::TYPE_ORDER_DESC + ])) { + $filters[] = $query; + } + } + + // If no filters, document matches + if (empty($filters)) { + return true; + } + + // Check each filter + foreach ($filters as $filter) { + $attribute = $filter->getAttribute(); + $values = $filter->getValues(); + $docValue = $doc->getAttribute($attribute); + + switch ($filter->getMethod()) { + case Query::TYPE_EQUAL: + if (!\in_array($docValue, $values)) { + return false; + } + break; + case Query::TYPE_NOT_EQUAL: + if (\in_array($docValue, $values)) { + return false; + } + break; + case Query::TYPE_CONTAINS: + $matches = false; + foreach ($values as $value) { + if (\is_array($docValue) && \in_array($value, $docValue)) { + $matches = true; + break; + } + } + if (!$matches) { + return false; + } + break; + case Query::TYPE_STARTS_WITH: + $matches = false; + foreach ($values as $value) { + if (\is_string($docValue) && \str_starts_with($docValue, $value)) { + $matches = true; + break; + } + } + if (!$matches) { + return false; + } + break; + case Query::TYPE_ENDS_WITH: + $matches = false; + foreach ($values as $value) { + if (\is_string($docValue) && \str_ends_with($docValue, $value)) { + $matches = true; + break; + } + } + if (!$matches) { + return false; + } + break; + case Query::TYPE_GREATER_THAN: + if (!($docValue > $values[0])) { + return false; + } + break; + case Query::TYPE_GREATER_THAN_EQUAL: + if (!($docValue >= $values[0])) { + return false; + } + break; + case Query::TYPE_LESSER_THAN: + if (!($docValue < $values[0])) { + return false; + } + break; + case Query::TYPE_LESSER_THAN_EQUAL: + if (!($docValue <= $values[0])) { + return false; + } + break; + case Query::TYPE_IS_NULL: + if (!\is_null($docValue)) { + return false; + } + break; + case Query::TYPE_IS_NOT_NULL: + if (\is_null($docValue)) { + return false; + } + break; + case Query::TYPE_BETWEEN: + if (!($docValue >= $values[0] && $docValue <= $values[1])) { + return false; + } + break; + } + } + + return true; + } + /** * Check if a document exists with transaction-aware logic */ diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 26d315e52e..a30fed47ed 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -129,7 +129,7 @@ class XList extends Action // Use transaction-aware document retrieval if transactionId is provided if ($transactionId !== null) { $documents = $transactionState->listDocuments($collectionTableId, $transactionId, $queries); - $total = count($documents); // For transaction-aware queries, we count the actual results + $total = $transactionState->countDocuments($collectionTableId, $transactionId, $queries); } elseif (! empty($selectQueries)) { // has selects, allow relationship on documents $documents = $dbForProject->find($collectionTableId, $queries); From 7fd2502dd5cc28364be986c4bf2e8c89f954579c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:03:34 +1300 Subject: [PATCH 246/385] Block client bulk upsert txn --- .../Databases/Http/Databases/Transactions/Operations/Create.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 0aca3e3f7c..20cf79551c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -87,6 +87,7 @@ class Create extends Action if (!$isAPIKey && !$isPrivilegedUser && \in_array($operation['action'], [ 'bulkCreate', 'bulkUpdate', + 'bulkUpsert', 'bulkDelete' ])) { throw new Exception(Exception::USER_UNAUTHORIZED); From f4830b1672f624b7e5ad3738c2e3859b2b4140be Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:03:46 +1300 Subject: [PATCH 247/385] Catch query exception --- .../Databases/Http/Databases/Transactions/Update.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index afa466c366..2513c09c64 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -18,6 +18,7 @@ use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; +use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Query; @@ -227,6 +228,11 @@ class Update extends Action 'status' => 'failed', ])); throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); + } catch (QueryException $e) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } }); From 5da2ca292af649ac6034bd8e9c26a16ce84e3847 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:03:54 +1300 Subject: [PATCH 248/385] Add missing injection --- .../Modules/Databases/Http/TablesDB/Transactions/Delete.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php index 28f273f566..46cd6b6c51 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php @@ -49,6 +49,7 @@ class Delete extends TransactionsDelete ->param('transactionId', '', new UID(), 'Transaction ID.') ->inject('response') ->inject('dbForProject') + ->inject('queueForDeletes') ->callback($this->action(...)); } } From d54058b59ade2721520beda1d8b809d2d36a7a29 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:12:19 +1300 Subject: [PATCH 249/385] Fix multi API event --- .../Databases/Http/Databases/Transactions/Update.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 2513c09c64..37642a0cca 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -266,12 +266,18 @@ class Update extends Action Query::equal('$sequence', [$collectionInternalId]) ]); + $groupId = $this->getGroupId(); + $resourceId = $this->getResourceId(); + $contextKey = $this->getContext(); + $resource = $this->getResource(); + $resourcePlural = $resource . 's'; + $queueForEvents ->setParam('databaseId', $database->getId()) ->setContext('database', $database) ->setParam('collectionId', $collection->getId()) ->setParam('tableId', $collection->getId()) - ->setContext('collection', $collection); + ->setContext($contextKey, $collection); $eventAction = ''; $documents = []; @@ -308,7 +314,7 @@ class Update extends Action $queueForEvents ->setParam('documentId', $docId) ->setParam('rowId', $docId) - ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].' . $eventAction); + ->setEvent("databases.[databaseId].{$contextKey}s.[{$groupId}].{$resourcePlural}.[{$resourceId}]." . $eventAction); $queueForRealtime->from($queueForEvents)->trigger(); $queueForFunctions->from($queueForEvents)->trigger(); From 44e7068a9a0fc49f15a666b00b4975eb193a4941 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:26:39 +1300 Subject: [PATCH 250/385] Fix syntax --- src/Appwrite/Utopia/Database/Validator/Operation.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 5cc3ca2135..911250c2bc 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -127,12 +127,6 @@ class Operation extends Validator } // If action requires documentId, it must be present -- if ( -- isset($this->requiresDocumentId[$value['action']]) && -- !\array_key_exists($this->documentIdName, $value) -- ) { -- $this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'"; -- return false; $actionRequiresDocumentId = ($this->requiresDocumentId[$value['action']] ?? false) === true; if ($actionRequiresDocumentId && !\array_key_exists($this->documentIdName, $value)) { $this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'"; From 522a4d2a62ab781526a8df9ba442519fcb4914e3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 00:39:14 +1300 Subject: [PATCH 251/385] Format --- src/Appwrite/Databases/TransactionState.php | 4 +++- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 2 +- src/Appwrite/Utopia/Database/Validator/Operation.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index f37366f72c..1f8e7f65c8 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -134,7 +134,9 @@ class TransactionState case 'upsert': $docId = $documentId ?? ($data['$id'] ?? null); - if (!$docId) { break; } + if (!$docId) { + break; + } $state[$collectionId][$docId] = [ 'action' => 'upsert', 'document' => new Document($data), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 2c8d26020d..a2156484a8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -144,7 +144,7 @@ class Upsert extends Action 'transactions', $transactionId, 'operations', - 1 + 1 ); }); diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 911250c2bc..2e3dc8e9e0 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -151,7 +151,7 @@ class Operation extends Validator $this->description = "Key 'data' must be an array"; return false; } - } else if (\array_key_exists('data', $value)) { + } elseif (\array_key_exists('data', $value)) { // Data is optional but if provided, must be an array if (!\is_array($value['data'])) { $this->description = "Key 'data' must be an array"; From 76d3dc3d5a12e8d4223ba82ad27d98de15980e3c Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 2 Oct 2025 18:27:20 +0530 Subject: [PATCH 252/385] * added senstive var copying in `from` method * added tests --- src/Appwrite/Event/Event.php | 1 + .../Realtime/RealtimeCustomClientTest.php | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index e9f3ccc2a2..16fe76bf8a 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -592,6 +592,7 @@ class Event $this->project = $event->getProject(); $this->user = $event->getUser(); $this->payload = $event->getPayload(); + $this->sensitive = $event->sensitive; $this->event = $event->getEvent(); $this->params = $event->getParams(); $this->context = $event->context; diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 3e57c5e9bc..ef39908658 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -2507,4 +2507,155 @@ class RealtimeCustomClientTest extends Scope $client->close(); } + + public function testRelationshipPayloadHidesRelatedDoc() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session + ]); + + $response = json_decode($client->receive(), true); + $this->assertArrayHasKey('type', $response); + $this->assertEquals('connected', $response['type']); + + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'db-rel' + ]); + $databaseId = $database['body']['$id']; + + $level1 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'level1', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'documentSecurity' => true, + ]); + $level1Id = $level1['body']['$id']; + + $level2 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'level2', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'documentSecurity' => true, + ]); + $level2Id = $level2['body']['$id']; + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$level1Id}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$level2Id}/attributes/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => false, + ]); + + sleep(2); + + // two-way one-to-one relationship from level1 to level2 + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$level1Id}/attributes/relationship", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'relatedCollectionId' => $level2Id, + 'type' => 'oneToOne', + 'twoWay' => true, + 'key' => 'level2Ref', + 'onDelete' => 'cascade', + ]); + + sleep(2); + + $doc2 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$level2Id}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ 'name' => 'L2' ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $doc2Id = $doc2['body']['$id']; + + $doc1 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$level1Id}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ 'name' => 'L1' ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $doc1Id = $doc1['body']['$id']; + + json_decode($client->receive(), true); + + $this->client->call(Client::METHOD_PATCH, "/databases/{$databaseId}/collections/{$level1Id}/documents/{$doc1Id}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'level2Ref' => $doc2Id, + ], + ]); + + // payload should not contain the relationship attribute 'level2Ref' + $event = json_decode($client->receive(), true); + $this->assertArrayHasKey('type', $event); + $this->assertEquals('event', $event['type']); + $this->assertArrayHasKey('data', $event); + $this->assertNotEmpty($event['data']); + $this->assertArrayHasKey('payload', $event['data']); + $this->assertArrayHasKey('$id', $event['data']['payload']); + $this->assertEquals($doc1Id, $event['data']['payload']['$id']); + $this->assertArrayNotHasKey('level2Ref', $event['data']['payload']); + + $client->close(); + } } From 770c58d33c501e4d3d870749bdc074113fd386fe Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal <114170980+Priyanshuthapliyal2005@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:28:35 +0000 Subject: [PATCH 253/385] fix(docs): replace broken table creation links with correct references --- app/config/specs/open-api3-1.8.x-client.json | 16 +++--- app/config/specs/open-api3-1.8.x-console.json | 54 +++++++++---------- app/config/specs/open-api3-1.8.x-server.json | 54 +++++++++---------- app/config/specs/open-api3-latest-client.json | 16 +++--- .../specs/open-api3-latest-console.json | 54 +++++++++---------- app/config/specs/open-api3-latest-server.json | 54 +++++++++---------- app/config/specs/swagger2-1.8.x-client.json | 16 +++--- app/config/specs/swagger2-1.8.x-console.json | 54 +++++++++---------- app/config/specs/swagger2-1.8.x-server.json | 54 +++++++++---------- app/config/specs/swagger2-latest-client.json | 16 +++--- app/config/specs/swagger2-latest-console.json | 54 +++++++++---------- app/config/specs/swagger2-latest-server.json | 54 +++++++++---------- docs/references/tablesdb/create-row.md | 2 +- docs/references/tablesdb/create-rows.md | 2 +- docs/references/tablesdb/create-table.md | 2 +- docs/references/tablesdb/upsert-row.md | 2 +- docs/references/tablesdb/upsert-rows.md | 2 +- .../Tables/Columns/Boolean/Create.php | 2 +- .../Tables/Columns/Boolean/Update.php | 2 +- .../TablesDB/Tables/Columns/Line/Create.php | 2 +- .../TablesDB/Tables/Columns/Line/Update.php | 2 +- .../TablesDB/Tables/Columns/Point/Create.php | 2 +- .../TablesDB/Tables/Columns/Point/Update.php | 2 +- .../Tables/Columns/Polygon/Create.php | 2 +- .../Tables/Columns/Polygon/Update.php | 2 +- .../TablesDB/Tables/Columns/String/Create.php | 2 +- .../TablesDB/Tables/Columns/String/Update.php | 2 +- .../Http/TablesDB/Tables/Indexes/Create.php | 2 +- .../Http/TablesDB/Tables/Indexes/Delete.php | 2 +- .../Http/TablesDB/Tables/Indexes/Get.php | 2 +- .../Http/TablesDB/Tables/Indexes/XList.php | 2 +- .../Http/TablesDB/Tables/Rows/Bulk/Delete.php | 2 +- .../Http/TablesDB/Tables/Rows/Create.php | 2 +- .../Http/TablesDB/Tables/Rows/Delete.php | 2 +- .../Http/TablesDB/Tables/Rows/Get.php | 2 +- .../Http/TablesDB/Tables/Rows/XList.php | 2 +- 36 files changed, 272 insertions(+), 272 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index d57b9f83b2..2038c1940a 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -7533,7 +7533,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -7562,7 +7562,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7624,7 +7624,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" } ], @@ -7652,7 +7652,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -7766,7 +7766,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -7805,7 +7805,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7866,7 +7866,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -8105,7 +8105,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index bdff664cbc..e0eccd3f69 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -33219,7 +33219,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -33717,7 +33717,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -33826,7 +33826,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35336,7 +35336,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35448,7 +35448,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35568,7 +35568,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35680,7 +35680,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35800,7 +35800,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35912,7 +35912,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36166,7 +36166,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36286,7 +36286,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36927,7 +36927,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37012,7 +37012,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37144,7 +37144,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37218,7 +37218,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37390,7 +37390,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -37419,7 +37419,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -37481,7 +37481,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -37507,7 +37507,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -37535,7 +37535,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -37588,7 +37588,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -37646,7 +37646,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -37865,7 +37865,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37961,7 +37961,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -38000,7 +38000,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -38061,7 +38061,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -38300,7 +38300,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 6b766dbdee..74be46d8d3 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -23633,7 +23633,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -24137,7 +24137,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -24247,7 +24247,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -25770,7 +25770,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -25883,7 +25883,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26004,7 +26004,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26117,7 +26117,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26238,7 +26238,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26351,7 +26351,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26607,7 +26607,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26728,7 +26728,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27375,7 +27375,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27461,7 +27461,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27594,7 +27594,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27669,7 +27669,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27757,7 +27757,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -27786,7 +27786,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -27849,7 +27849,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -27876,7 +27876,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -27906,7 +27906,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -27959,7 +27959,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -28018,7 +28018,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -28240,7 +28240,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -28338,7 +28338,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -28377,7 +28377,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -28439,7 +28439,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -28684,7 +28684,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index d57b9f83b2..2038c1940a 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -7533,7 +7533,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -7562,7 +7562,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7624,7 +7624,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" } ], @@ -7652,7 +7652,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -7766,7 +7766,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -7805,7 +7805,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7866,7 +7866,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -8105,7 +8105,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index bdff664cbc..e0eccd3f69 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -33219,7 +33219,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -33717,7 +33717,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -33826,7 +33826,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35336,7 +35336,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35448,7 +35448,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35568,7 +35568,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35680,7 +35680,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35800,7 +35800,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -35912,7 +35912,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36166,7 +36166,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36286,7 +36286,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -36927,7 +36927,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37012,7 +37012,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37144,7 +37144,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37218,7 +37218,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37390,7 +37390,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -37419,7 +37419,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -37481,7 +37481,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -37507,7 +37507,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -37535,7 +37535,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -37588,7 +37588,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -37646,7 +37646,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -37865,7 +37865,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37961,7 +37961,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -38000,7 +38000,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -38061,7 +38061,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -38300,7 +38300,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 6b766dbdee..74be46d8d3 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -23633,7 +23633,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -24137,7 +24137,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -24247,7 +24247,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -25770,7 +25770,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -25883,7 +25883,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26004,7 +26004,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26117,7 +26117,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26238,7 +26238,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26351,7 +26351,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26607,7 +26607,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -26728,7 +26728,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27375,7 +27375,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27461,7 +27461,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27594,7 +27594,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27669,7 +27669,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27757,7 +27757,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", @@ -27786,7 +27786,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -27849,7 +27849,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -27876,7 +27876,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -27906,7 +27906,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "schema": { "type": "string", @@ -27959,7 +27959,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -28018,7 +28018,7 @@ "model": "#\/components\/schemas\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -28240,7 +28240,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -28338,7 +28338,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -28377,7 +28377,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -28439,7 +28439,7 @@ "model": "#\/components\/schemas\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -28684,7 +28684,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index c2628533d0..3003de22af 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -7612,7 +7612,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -7644,7 +7644,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7701,7 +7701,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" } ], @@ -7727,7 +7727,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -7838,7 +7838,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -7878,7 +7878,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7934,7 +7934,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -8163,7 +8163,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index ee3702d27d..f222d3dff6 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -33348,7 +33348,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -33834,7 +33834,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -33943,7 +33943,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35442,7 +35442,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35546,7 +35546,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35656,7 +35656,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35760,7 +35760,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35870,7 +35870,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35974,7 +35974,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36220,7 +36220,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36342,7 +36342,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36964,7 +36964,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37046,7 +37046,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37177,7 +37177,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37249,7 +37249,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37409,7 +37409,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -37441,7 +37441,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -37498,7 +37498,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -37524,7 +37524,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -37550,7 +37550,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -37609,7 +37609,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -37663,7 +37663,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -37875,7 +37875,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37965,7 +37965,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -38005,7 +38005,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -38061,7 +38061,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -38290,7 +38290,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index ff5056b35a..db04d55cd0 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -23817,7 +23817,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -24309,7 +24309,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -24419,7 +24419,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -25931,7 +25931,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26036,7 +26036,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26147,7 +26147,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26252,7 +26252,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26363,7 +26363,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26468,7 +26468,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26716,7 +26716,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26839,7 +26839,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27467,7 +27467,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27550,7 +27550,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27682,7 +27682,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27755,7 +27755,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27836,7 +27836,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -27868,7 +27868,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -27926,7 +27926,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -27953,7 +27953,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -27981,7 +27981,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -28040,7 +28040,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -28095,7 +28095,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -28310,7 +28310,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -28402,7 +28402,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -28442,7 +28442,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -28499,7 +28499,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -28734,7 +28734,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index c2628533d0..3003de22af 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -7612,7 +7612,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -7644,7 +7644,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7701,7 +7701,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" } ], @@ -7727,7 +7727,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -7838,7 +7838,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -7878,7 +7878,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -7934,7 +7934,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -8163,7 +8163,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index ee3702d27d..f222d3dff6 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -33348,7 +33348,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -33834,7 +33834,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -33943,7 +33943,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35442,7 +35442,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35546,7 +35546,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35656,7 +35656,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35760,7 +35760,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35870,7 +35870,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -35974,7 +35974,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36220,7 +36220,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36342,7 +36342,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -36964,7 +36964,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37046,7 +37046,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37177,7 +37177,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37249,7 +37249,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37409,7 +37409,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -37441,7 +37441,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -37498,7 +37498,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -37524,7 +37524,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -37550,7 +37550,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -37609,7 +37609,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -37663,7 +37663,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -37875,7 +37875,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37965,7 +37965,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -38005,7 +38005,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -38061,7 +38061,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -38290,7 +38290,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index ff5056b35a..db04d55cd0 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -23817,7 +23817,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Table", @@ -24309,7 +24309,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -24419,7 +24419,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -25931,7 +25931,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26036,7 +26036,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26147,7 +26147,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26252,7 +26252,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26363,7 +26363,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26468,7 +26468,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26716,7 +26716,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -26839,7 +26839,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27467,7 +27467,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27550,7 +27550,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27682,7 +27682,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27755,7 +27755,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27836,7 +27836,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdbdb#tablesdbCreate).", + "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", @@ -27868,7 +27868,7 @@ "tags": [ "tablesDB" ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -27926,7 +27926,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-row.md" }, { @@ -27953,7 +27953,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/create-rows.md" } ], @@ -27981,7 +27981,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable). Make sure to define columns before creating rows.", "required": true, "type": "string", "x-example": "", @@ -28040,7 +28040,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "responses": { "201": { "description": "Rows List", @@ -28095,7 +28095,7 @@ "model": "#\/definitions\/rowList" } ], - "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.\n", + "description": "Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.\n", "demo": "tablesdb\/upsert-rows.md" } ], @@ -28310,7 +28310,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -28402,7 +28402,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -28442,7 +28442,7 @@ "tags": [ "tablesDB" ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "responses": { "201": { "description": "Row", @@ -28499,7 +28499,7 @@ "model": "#\/definitions\/row" } ], - "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreateTable) API or directly from your database console.", + "description": "Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable) API or directly from your database console.", "demo": "tablesdb\/upsert-row.md" } ], @@ -28734,7 +28734,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/tablesdb#tablesDBCreate).", + "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", diff --git a/docs/references/tablesdb/create-row.md b/docs/references/tablesdb/create-row.md index 9b25555eeb..2ddae7e5a7 100644 --- a/docs/references/tablesdb/create-row.md +++ b/docs/references/tablesdb/create-row.md @@ -1 +1 @@ -Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreateTable) API or directly from your database console. \ No newline at end of file +Create a new Row. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/tablesdb/create-rows.md b/docs/references/tablesdb/create-rows.md index 6a19d7bc9f..b8b5e93582 100644 --- a/docs/references/tablesdb/create-rows.md +++ b/docs/references/tablesdb/create-rows.md @@ -1 +1 @@ -Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreateTable) API or directly from your database console. \ No newline at end of file +Create new Rows. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/tablesdb/create-table.md b/docs/references/tablesdb/create-table.md index 006e8794df..c240440bf2 100644 --- a/docs/references/tablesdb/create-table.md +++ b/docs/references/tablesdb/create-table.md @@ -1 +1 @@ -Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreateTable) API or directly from your database console. \ No newline at end of file +Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/tablesdb/upsert-row.md b/docs/references/tablesdb/upsert-row.md index 26edd7ad04..2ae62fedc9 100644 --- a/docs/references/tablesdb/upsert-row.md +++ b/docs/references/tablesdb/upsert-row.md @@ -1 +1 @@ -Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreateTable) API or directly from your database console. \ No newline at end of file +Create or update a Row. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/tablesdb/upsert-rows.md b/docs/references/tablesdb/upsert-rows.md index f980d8c30d..52f9d29398 100644 --- a/docs/references/tablesdb/upsert-rows.md +++ b/docs/references/tablesdb/upsert-rows.md @@ -1 +1 @@ -Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreateTable) API or directly from your database console. +Create or update Rows. Before using this route, you should create a new table resource using either a [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable) API or directly from your database console. diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php index 5222d2e133..bb4bef35e8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php @@ -50,7 +50,7 @@ class Create extends BooleanCreate ] )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Boolean(), 'Default value for column when not provided. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php index 3c6ef50813..2d8789e394 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php @@ -53,7 +53,7 @@ class Update extends BooleanUpdate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Boolean()), 'Default value for column when not provided. Cannot be set when column is required.') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php index f60d4dd5b8..9903ad3a7e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Create.php @@ -53,7 +53,7 @@ class Create extends LineCreate ] )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php index 19c6df202d..04b514afb8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php @@ -55,7 +55,7 @@ class Update extends LineUpdate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php index 47d97e8077..ed95e4629c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Create.php @@ -53,7 +53,7 @@ class Create extends PointCreate ] )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for column when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php index 2e98bf2cf9..f29e5bb27a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php @@ -55,7 +55,7 @@ class Update extends PointUpdate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for column when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php index 371d5f8fd5..c50c5acd5c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Create.php @@ -53,7 +53,7 @@ class Create extends PolygonCreate ] )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for column when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php index c5654b77d4..9f689717c3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php @@ -55,7 +55,7 @@ class Update extends PolygonUpdate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for column when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when column is required.', true) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php index 14f0c8321e..efe7dd513f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php @@ -53,7 +53,7 @@ class Create extends StringCreate ] )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Validator::TYPE_INTEGER), 'Column size for text columns, in number of characters.') ->param('required', null, new Boolean(), 'Is column required?') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php index fc45557f3b..7ba813aedd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php @@ -56,7 +56,7 @@ class Update extends StringUpdate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Column Key.') ->param('required', null, new Boolean(), 'Is column required?') ->param('default', null, new Nullable(new Text(0, 0)), 'Default value for column when not provided. Cannot be set when column is required.') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php index a2a5c8b453..fdbe91f5f7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php @@ -56,7 +56,7 @@ class Create extends IndexCreate contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', null, new Key(), 'Index Key.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.') ->param('columns', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of columns to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' columns are allowed, each 32 characters long.') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php index 586bad78f4..dd97c4bdc7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php @@ -55,7 +55,7 @@ class Delete extends IndexDelete contentType: ContentType::NONE )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php index 3f2978b547..907b396072 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Get.php @@ -48,7 +48,7 @@ class Get extends IndexGet contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', null, new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php index c275fd2771..835e543d07 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/XList.php @@ -48,7 +48,7 @@ class XList extends IndexXList contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('queries', [], new Indexes(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following columns: ' . implode(', ', Indexes::ALLOWED_ATTRIBUTES), true) ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php index c9729d714d..a99316394a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php @@ -54,7 +54,7 @@ class Delete extends DocumentsDelete contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php index cf38bac63b..a39d9adbda 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php @@ -94,7 +94,7 @@ class Create extends DocumentCreate ]) ->param('databaseId', '', new UID(), 'Database ID.') ->param('rowId', '', new CustomId(), 'Row ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate). Make sure to define columns before creating rows.') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable). Make sure to define columns before creating rows.') ->param('data', [], new JSON(), 'Row data as JSON object.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('rows', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of rows data as JSON objects.', true, ['plan']) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php index 7ac954c5dd..72f4ef3d94 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php @@ -59,7 +59,7 @@ class Delete extends DocumentDelete contentType: ContentType::NONE )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('rowId', '', new UID(), 'Row ID.') ->inject('requestTimestamp') ->inject('response') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php index 5704f75d82..faf1584d0d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php @@ -49,7 +49,7 @@ class Get extends DocumentGet contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('rowId', '', new UID(), 'Row ID.') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php index 5d503f1c59..cbbbf9520d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php @@ -49,7 +49,7 @@ class XList extends DocumentXList contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TableDB service [server integration](https://appwrite.io/docs/server/tablesdbdb#tablesdbCreate).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TableDB service [server integration](https://appwrite.io/docs/products/databases/tables#create-table).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') ->inject('dbForProject') From a61385e5340f24cf2a12f1b97dbb21141ca65ab0 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 3 Oct 2025 08:43:54 +0530 Subject: [PATCH 254/385] update versions --- app/config/platforms.php | 18 +++++++++--------- src/Appwrite/Platform/Tasks/SDKs.php | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 5b42381da3..fff382df3d 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '20.2.0', + 'version' => '21.0.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '19.2.0', + 'version' => '20s.0.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '12.2.0', + 'version' => '13.0.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '10.2.0', + 'version' => '11.0.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '19.2.0', + 'version' => '20.0.0', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '18.2.0', + 'version' => '19.0.0', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '18.2.0', + 'version' => '19.0.0', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '11.2.0', + 'version' => '12.0.0', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '12.2.0', + 'version' => '13.0.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 90aa07f726..37ed2c9f3b 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -318,7 +318,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND '); Console::success("Pushed {$language['name']} SDK to {$gitUrl}"); - if ($createPr) { $prTitle = "feat: {$language['name']} SDK update for version {$language['version']}"; $prBody = "This PR contains updates to the {$language['name']} SDK for version {$language['version']}."; @@ -353,7 +352,25 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } else { $errorMessage = implode("\n", $prOutput); if (strpos($errorMessage, 'already exists') !== false) { - Console::warning("Pull request already exists for {$language['name']} SDK"); + Console::warning("Pull request already exists for {$language['name']} SDK, updating title and body..."); + + $updateCommand = 'cd ' . $target . ' && \ + gh pr edit "' . $gitBranch . '" \ + --repo "' . $repoName . '" \ + --title "' . $prTitle . '" \ + --body "' . $prBody . '" \ + 2>&1'; + + $updateOutput = []; + $updateReturnCode = 0; + \exec($updateCommand, $updateOutput, $updateReturnCode); + + if ($updateReturnCode === 0) { + Console::success("Successfully updated pull request for {$language['name']} SDK"); + } else { + $updateErrorMessage = implode("\n", $updateOutput); + Console::error("Failed to update pull request for {$language['name']} SDK: " . $updateErrorMessage); + } } else { Console::error("Failed to create pull request for {$language['name']} SDK: " . $errorMessage); } From 848b932ea1c94d89663a4893b0f365e8c80385da Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 3 Oct 2025 08:46:25 +0530 Subject: [PATCH 255/385] fix flutter version --- app/config/platforms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index fff382df3d..aeed725bf7 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '20s.0.0', + 'version' => '21.0.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, From 71b2164154ae844a6c4bbe024da2c03f8689ae0e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 16:58:23 +1300 Subject: [PATCH 256/385] Clean up state helper --- src/Appwrite/Databases/TransactionState.php | 484 +++++++++++------- .../Http/Databases/Transactions/Update.php | 117 ++++- 2 files changed, 423 insertions(+), 178 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 1f8e7f65c8..85ecb41ae2 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -8,6 +8,11 @@ use Utopia\Database\Query; /** * Service for managing transaction state and providing transaction-aware document operations + * + * This class provides methods to: + * - Query documents with transaction awareness (getDocument, listDocuments, countDocuments) + * - Apply bulk operations to transaction state for cross-operation visibility + * - Replay transaction operations to build current state */ class TransactionState { @@ -18,161 +23,15 @@ class TransactionState $this->dbForProject = $dbForProject; } - /** - * Apply projection (select) semantics from queries to a document - */ - private function applyProjection(Document $doc, array $queries): Document - { - if (empty($queries)) { - return $doc; - } - - // Extract selections from queries - $selections = []; - foreach ($queries as $query) { - if ($query->getMethod() === Query::TYPE_SELECT) { - $values = $query->getValues(); - foreach ($values as $value) { - // Skip relationship selections (containing '.') - if (!\str_contains($value, '.')) { - $selections[] = $value; - } - } - } - } - - // If no selections or wildcard present, return document as-is - if (empty($selections) || \in_array('*', $selections)) { - return $doc; - } - - // Create a new document with only selected attributes - $projected = new Document(); - - // Always preserve internal attributes - $projected->setAttribute('$id', $doc->getId()); - $projected->setAttribute('$collection', $doc->getCollection()); - $projected->setAttribute('$createdAt', $doc->getCreatedAt()); - $projected->setAttribute('$updatedAt', $doc->getUpdatedAt()); - if ($doc->offsetExists('$permissions')) { - $projected->setAttribute('$permissions', $doc->getPermissions()); - } - - // Add selected attributes - foreach ($selections as $attribute) { - if ($doc->offsetExists($attribute)) { - $projected->setAttribute($attribute, $doc->getAttribute($attribute)); - } - } - - return $projected; - } - - /** - * Get the current state of a transaction by replaying its operations - */ - private function getTransactionState(string $transactionId): array - { - $transaction = $this->dbForProject->getDocument('transactions', $transactionId); - if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') { - return []; - } - - // Fetch operations ordered by sequence to replay in exact order - $operations = $this->dbForProject->find('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - ]); - - $state = []; - - foreach ($operations as $operation) { - $databaseInternalId = $operation['databaseInternalId']; - $collectionInternalId = $operation['collectionInternalId']; - $collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; - $documentId = $operation['documentId']; - $action = $operation['action']; - $data = $operation['data']; - - if ($data instanceof Document) { - $data = $data->getArrayCopy(); - } - - switch ($action) { - case 'create': - $docId = $documentId ?? ($data['$id'] ?? null); - if ($docId) { - $state[$collectionId][$docId] = [ - 'action' => 'create', - 'document' => new Document($data), - 'exists' => true - ]; - } - break; - case 'update': - if (isset($state[$collectionId][$documentId])) { - // Update existing document in transaction state - $existingDocument = $state[$collectionId][$documentId]['document']; - foreach ($data as $key => $value) { - if ($key !== '$id') { - $existingDocument->setAttribute($key, $value); - } - } - // Only set action to 'update' if it's not already 'create' or 'upsert' - $currentAction = $state[$collectionId][$documentId]['action']; - if ($currentAction !== 'create' && $currentAction !== 'upsert') { - $state[$collectionId][$documentId]['action'] = 'update'; - } - } else { - // Document doesn't exist in transaction state, will be merged with committed version - $state[$collectionId][$documentId] = [ - 'action' => 'update', - 'document' => new Document($data), - 'exists' => true - ]; - } - break; - - case 'upsert': - $docId = $documentId ?? ($data['$id'] ?? null); - if (!$docId) { - break; - } - $state[$collectionId][$docId] = [ - 'action' => 'upsert', - 'document' => new Document($data), - 'exists' => true - ]; - break; - - case 'delete': - $state[$collectionId][$documentId] = [ - 'action' => 'delete', - 'exists' => false - ]; - break; - - case 'bulkCreate': - if (is_array($data)) { - foreach ($data as $doc) { - if ($doc instanceof Document) { - $doc = $doc->getArrayCopy(); - } - $state[$collectionId][$doc['$id']] = [ - 'action' => 'create', - 'document' => new Document($doc), - 'exists' => true - ]; - } - } - break; - } - } - - return $state; - } /** * Get a document with transaction-aware logic + * + * @param string $collectionId Collection ID + * @param string $documentId Document ID + * @param string|null $transactionId Optional transaction ID + * @param array $queries Optional query filters + * @return Document */ public function getDocument( string $collectionId, @@ -187,7 +46,6 @@ class TransactionState $state = $this->getTransactionState($transactionId); - // Check if document exists in transaction state if (isset($state[$collectionId][$documentId])) { $docState = $state[$collectionId][$documentId]; @@ -212,8 +70,7 @@ class TransactionState $committedDoc->setAttribute($key, $value); } } - // committedDoc already has projection applied by dbForProject->getDocument() - // But we need to reapply in case transaction added new fields + // Reapply projection in case transaction added new fields return $this->applyProjection($committedDoc, $queries); } elseif ($docState['action'] === 'upsert') { // Upsert created a new document since committed doc doesn't exist @@ -228,6 +85,11 @@ class TransactionState /** * List documents with transaction-aware logic + * + * @param string $collectionId Collection ID + * @param string|null $transactionId Optional transaction ID + * @param array $queries Optional query filters + * @return array Array of Document objects */ public function listDocuments( string $collectionId, @@ -280,6 +142,11 @@ class TransactionState /** * Count documents with transaction-aware logic + * + * @param string $collectionId Collection ID + * @param string|null $transactionId Optional transaction ID + * @param array $queries Optional query filters + * @return int Document count */ public function countDocuments( string $collectionId, @@ -302,7 +169,6 @@ class TransactionState } // Build a set of committed document IDs that match the query - // We need to find which documents match the filters $committedDocs = $this->dbForProject->find($collectionId, $queries); $committedDocIds = []; foreach ($committedDocs as $doc) { @@ -320,9 +186,6 @@ class TransactionState } } elseif ($docState['action'] === 'create') { // Document was created in transaction - // We need to check if it would match the query filters - // For now, we'll conservatively add it if no filters are present - // or apply basic filter matching if ($this->documentMatchesFilters($docState['document'], $queries)) { $adjustedCount++; // New document that matches } @@ -348,7 +211,285 @@ class TransactionState } /** - * Check if a document matches filter queries (simplified implementation) + * Check if a document exists with transaction-aware logic + * + * @param string $collectionId Collection ID + * @param string $documentId Document ID + * @param string|null $transactionId Optional transaction ID + * @return bool True if document exists + */ + public function documentExists( + string $collectionId, + string $documentId, + ?string $transactionId = null + ): bool { + $doc = $this->getDocument($collectionId, $documentId, $transactionId); + return !$doc->isEmpty(); + } + + /** + * Apply bulk update to documents in transaction state that match queries + * + * This allows bulk operations within a transaction to see each other's changes. + * + * @param string $collectionId Collection ID + * @param Document $updateData Document with update values + * @param array $queries Query filters to match documents + * @param array &$state Transaction state (passed by reference) + * @return void + */ + public function applyBulkUpdateToState( + string $collectionId, + Document $updateData, + array $queries, + array &$state + ): void { + if (!isset($state[$collectionId])) { + return; + } + + foreach ($state[$collectionId] as $docId => $doc) { + if ($this->documentMatchesFilters($doc, $queries)) { + // Apply the update to the state document + foreach ($updateData->getArrayCopy() as $key => $value) { + if ($key !== '$id') { + $doc->setAttribute($key, $value); + } + } + } + } + } + + /** + * Apply bulk delete to documents in transaction state that match queries + * + * This allows bulk operations within a transaction to see each other's changes. + * + * @param string $collectionId Collection ID + * @param array $queries Query filters to match documents + * @param array &$state Transaction state (passed by reference) + * @return void + */ + public function applyBulkDeleteToState( + string $collectionId, + array $queries, + array &$state + ): void { + if (!isset($state[$collectionId])) { + return; + } + + foreach ($state[$collectionId] as $docId => $doc) { + if ($this->documentMatchesFilters($doc, $queries)) { + unset($state[$collectionId][$docId]); + } + } + } + + /** + * Apply bulk upsert to documents in transaction state + * + * This allows bulk operations within a transaction to see each other's changes. + * + * @param string $collectionId Collection ID + * @param array $documents Array of Document objects to upsert + * @param array &$state Transaction state (passed by reference) + * @return void + */ + public function applyBulkUpsertToState( + string $collectionId, + array $documents, + array &$state + ): void { + foreach ($documents as $doc) { + if (!($doc instanceof Document)) { + continue; + } + + $docId = $doc->getId(); + if (!$docId) { + continue; + } + + // If document exists in state, update it; otherwise it will be handled by DB upsert + if (isset($state[$collectionId][$docId])) { + // Apply updates to existing state document + foreach ($doc->getArrayCopy() as $key => $value) { + if ($key !== '$id') { + $state[$collectionId][$docId]->setAttribute($key, $value); + } + } + } + } + } + + /** + * Get the current state of a transaction by replaying its operations + * + * @param string $transactionId Transaction ID + * @return array State array with structure: [collectionId => [docId => ['action' => ..., 'document' => ..., 'exists' => ...]]] + */ + private function getTransactionState(string $transactionId): array + { + $transaction = $this->dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') { + return []; + } + + // Fetch operations ordered by sequence to replay in exact order + $operations = $this->dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + ]); + + $state = []; + + foreach ($operations as $operation) { + $databaseInternalId = $operation['databaseInternalId']; + $collectionInternalId = $operation['collectionInternalId']; + $collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; + $documentId = $operation['documentId']; + $action = $operation['action']; + $data = $operation['data']; + + if ($data instanceof Document) { + $data = $data->getArrayCopy(); + } + + switch ($action) { + case 'create': + $docId = $documentId ?? ($data['$id'] ?? null); + if ($docId) { + $state[$collectionId][$docId] = [ + 'action' => 'create', + 'document' => new Document($data), + 'exists' => true + ]; + } + break; + + case 'update': + if (isset($state[$collectionId][$documentId])) { + // Update existing document in transaction state + $existingDocument = $state[$collectionId][$documentId]['document']; + foreach ($data as $key => $value) { + if ($key !== '$id') { + $existingDocument->setAttribute($key, $value); + } + } + // Only set action to 'update' if it's not already 'create' or 'upsert' + $currentAction = $state[$collectionId][$documentId]['action']; + if ($currentAction !== 'create' && $currentAction !== 'upsert') { + $state[$collectionId][$documentId]['action'] = 'update'; + } + } else { + // Document doesn't exist in transaction state, will be merged with committed version + $state[$collectionId][$documentId] = [ + 'action' => 'update', + 'document' => new Document($data), + 'exists' => true + ]; + } + break; + + case 'upsert': + $docId = $documentId ?? ($data['$id'] ?? null); + if (!$docId) { + break; + } + $state[$collectionId][$docId] = [ + 'action' => 'upsert', + 'document' => new Document($data), + 'exists' => true + ]; + break; + + case 'delete': + $state[$collectionId][$documentId] = [ + 'action' => 'delete', + 'exists' => false + ]; + break; + + case 'bulkCreate': + if (\is_array($data)) { + foreach ($data as $doc) { + if ($doc instanceof Document) { + $doc = $doc->getArrayCopy(); + } + $state[$collectionId][$doc['$id']] = [ + 'action' => 'create', + 'document' => new Document($doc), + 'exists' => true + ]; + } + } + break; + } + } + + return $state; + } + + /** + * Apply projection (select) semantics from queries to a document + * + * @param Document $doc Document to apply projection to + * @param array $queries Query array that may contain select queries + * @return Document Projected document + */ + private function applyProjection(Document $doc, array $queries): Document + { + if (empty($queries)) { + return $doc; + } + + // Extract selections from queries + $selections = []; + foreach ($queries as $query) { + if ($query->getMethod() === Query::TYPE_SELECT) { + $values = $query->getValues(); + foreach ($values as $value) { + // Skip relationship selections (containing '.') + if (!\str_contains($value, '.')) { + $selections[] = $value; + } + } + } + } + + // If no selections or wildcard present, return document as-is + if (empty($selections) || \in_array('*', $selections)) { + return $doc; + } + + // Create a new document with only selected attributes + $projected = new Document(); + + // Always preserve internal attributes + $projected->setAttribute('$id', $doc->getId()); + $projected->setAttribute('$collection', $doc->getCollection()); + $projected->setAttribute('$createdAt', $doc->getCreatedAt()); + $projected->setAttribute('$updatedAt', $doc->getUpdatedAt()); + if ($doc->offsetExists('$permissions')) { + $projected->setAttribute('$permissions', $doc->getPermissions()); + } + + // Add selected attributes + foreach ($selections as $attribute) { + if ($doc->offsetExists($attribute)) { + $projected->setAttribute($attribute, $doc->getAttribute($attribute)); + } + } + + return $projected; + } + + /** + * Check if a document matches filter queries + * + * @param Document $doc Document to check + * @param array $queries Query filters + * @return bool True if document matches all filters */ private function documentMatchesFilters(Document $doc, array $queries): bool { @@ -387,11 +528,13 @@ class TransactionState return false; } break; + case Query::TYPE_NOT_EQUAL: if (\in_array($docValue, $values)) { return false; } break; + case Query::TYPE_CONTAINS: $matches = false; foreach ($values as $value) { @@ -404,6 +547,7 @@ class TransactionState return false; } break; + case Query::TYPE_STARTS_WITH: $matches = false; foreach ($values as $value) { @@ -416,6 +560,7 @@ class TransactionState return false; } break; + case Query::TYPE_ENDS_WITH: $matches = false; foreach ($values as $value) { @@ -428,36 +573,43 @@ class TransactionState return false; } break; + case Query::TYPE_GREATER_THAN: if (!($docValue > $values[0])) { return false; } break; + case Query::TYPE_GREATER_THAN_EQUAL: if (!($docValue >= $values[0])) { return false; } break; + case Query::TYPE_LESSER_THAN: if (!($docValue < $values[0])) { return false; } break; + case Query::TYPE_LESSER_THAN_EQUAL: if (!($docValue <= $values[0])) { return false; } break; + case Query::TYPE_IS_NULL: if (!\is_null($docValue)) { return false; } break; + case Query::TYPE_IS_NOT_NULL: if (\is_null($docValue)) { return false; } break; + case Query::TYPE_BETWEEN: if (!($docValue >= $values[0] && $docValue <= $values[1])) { return false; @@ -468,16 +620,4 @@ class TransactionState return true; } - - /** - * Check if a document exists with transaction-aware logic - */ - public function documentExists( - string $collectionId, - string $documentId, - ?string $transactionId = null - ): bool { - $doc = $this->getDocument($collectionId, $documentId, $transactionId); - return !$doc->isEmpty(); - } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 37642a0cca..19ba8f10d8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; +use Appwrite\Databases\TransactionState; use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; @@ -66,6 +67,7 @@ class Update extends Action ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) ->inject('response') ->inject('dbForProject') + ->inject('transactionState') ->inject('queueForDeletes') ->inject('queueForEvents') ->inject('queueForStatsUsage') @@ -81,6 +83,7 @@ class Update extends Action * @param bool $rollback * @param UtopiaResponse $response * @param Database $dbForProject + * @param TransactionState $transactionState * @param Delete $queueForDeletes * @param Event $queueForEvents * @param StatsUsage $queueForStatsUsage @@ -96,7 +99,7 @@ class Update extends Action * @throws Structure * @throws \Utopia\Exception */ - public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void + public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void { if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); @@ -182,13 +185,13 @@ class Update extends Action $this->handleBulkCreateOperation($dbForProject, $collectionId, $data, $createdAt, $state); break; case 'bulkUpdate': - $this->handleBulkUpdateOperation($dbForProject, $collectionId, $data, $createdAt, $state); + $this->handleBulkUpdateOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); break; case 'bulkUpsert': - $this->handleBulkUpsertOperation($dbForProject, $collectionId, $data, $createdAt, $state); + $this->handleBulkUpsertOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); break; case 'bulkDelete': - $this->handleBulkDeleteOperation($dbForProject, $collectionId, $data, $createdAt, $state); + $this->handleBulkDeleteOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); break; } } @@ -348,6 +351,14 @@ class Update extends Action /** * Handle create operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string|null $documentId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws \Utopia\Database\Exception */ private function handleCreateOperation( @@ -371,6 +382,14 @@ class Update extends Action /** * Handle update operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string $documentId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws ConflictException * @throws \Utopia\Database\Exception */ @@ -410,6 +429,14 @@ class Update extends Action /** * Handle upsert operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string|null $documentId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws \Utopia\Database\Exception */ private function handleUpsertOperation( @@ -442,6 +469,15 @@ class Update extends Action /** * Handle delete operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string $documentId + * @param \DateTime $createdAt + * @param array &$state + * @return void + * @throws \Utopia\Database\Exception + * @throws NotFoundException */ private function handleDeleteOperation( Database $dbForProject, @@ -473,6 +509,16 @@ class Update extends Action /** * Handle increment operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string $documentId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void + * @throws ConflictException + * @throws \Utopia\Database\Exception */ private function handleIncrementOperation( Database $dbForProject, @@ -510,6 +556,16 @@ class Update extends Action /** * Handle decrement operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param string $documentId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void + * @throws ConflictException + * @throws \Utopia\Database\Exception */ private function handleDecrementOperation( Database $dbForProject, @@ -547,6 +603,14 @@ class Update extends Action /** * Handle bulk create operation + * + * @param Database $dbForProject + * @param string $collectionId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void + * @throws \Utopia\Database\Exception */ private function handleBulkCreateOperation( Database $dbForProject, @@ -573,22 +637,33 @@ class Update extends Action /** * Handle bulk update operation with manual timestamp checking + * + * @param Database $dbForProject + * @param TransactionState $transactionState + * @param string $collectionId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception\Query * @throws ConflictException */ private function handleBulkUpdateOperation( Database $dbForProject, + TransactionState $transactionState, string $collectionId, array $data, \DateTime $createdAt, array &$state ): void { $queries = Query::parseQueries($data['queries'] ?? []); + $updateData = new Document($data['data']); + // First, update documents in the committed database $dbForProject->updateDocuments( $collectionId, - new Document($data['data']), + $updateData, $queries, onNext: function (Document $updated, Document $old) use (&$state, $collectionId, $createdAt) { // Check if this document was created/modified in this transaction @@ -605,14 +680,27 @@ class Update extends Action $state[$collectionId][$updated->getId()] = $updated; } ); + + // Also update documents in the transaction state that match the query + $transactionState->applyBulkUpdateToState($collectionId, $updateData, $queries, $state); } /** * Handle bulk upsert operation with manual timestamp checking + * + * @param Database $dbForProject + * @param TransactionState $transactionState + * @param string $collectionId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws ConflictException + * @throws \Utopia\Database\Exception */ private function handleBulkUpsertOperation( Database $dbForProject, + TransactionState $transactionState, string $collectionId, array $data, \DateTime $createdAt, @@ -623,7 +711,11 @@ class Update extends Action return $doc instanceof Document ? $doc : new Document($doc); }, $data); - // Run bulk upsert without timestamp wrapper, checking manually in callback + // First, apply upserts to documents in the transaction state + // This ensures documents created in this transaction are updated properly + $transactionState->applyBulkUpsertToState($collectionId, $documents, $state); + + // Then run bulk upsert on committed database, checking manually in callback $dbForProject->upsertDocuments( $collectionId, $documents, @@ -649,11 +741,21 @@ class Update extends Action /** * Handle bulk delete operation with manual timestamp checking + * + * @param Database $dbForProject + * @param TransactionState $transactionState + * @param string $collectionId + * @param array $data + * @param \DateTime $createdAt + * @param array &$state + * @return void * @throws \Utopia\Database\Exception\Query * @throws ConflictException + * @throws \Utopia\Database\Exception */ private function handleBulkDeleteOperation( Database $dbForProject, + TransactionState $transactionState, string $collectionId, array $data, \DateTime $createdAt, @@ -681,5 +783,8 @@ class Update extends Action } } ); + + // Also delete documents in the transaction state that match the query + $transactionState->applyBulkDeleteToState($collectionId, $queries, $state); } } From 59ae403391892b33a4d67e1eeafcc18061ea1a8f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 17:34:34 +1300 Subject: [PATCH 257/385] Add more validation tests --- .../Http/TablesDB/Transactions/Update.php | 1 + .../Utopia/Database/Validator/Operation.php | 45 ++ .../Legacy/Transactions/TransactionsBase.php | 351 ++++++++++++++ .../Transactions/TransactionsBase.php | 449 ++++++++++++++++++ 4 files changed, 846 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index bcfb2db406..6bdf6df7ef 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -52,6 +52,7 @@ class Update extends TransactionsUpdate ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) ->inject('response') ->inject('dbForProject') + ->inject('transactionState') ->inject('queueForDeletes') ->inject('queueForEvents') ->inject('queueForStatsUsage') diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 2e3dc8e9e0..4e421689e3 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -159,6 +159,51 @@ class Operation extends Validator } } + // Bulk operation specific validations + $action = $value['action']; + + // BulkUpdate and BulkDelete require queries + if (\in_array($action, ['bulkUpdate', 'bulkDelete'])) { + if (!\array_key_exists('data', $value) || !\is_array($value['data'])) { + $this->description = "Key 'data' must be an array for {$action}"; + return false; + } + if (!\array_key_exists('queries', $value['data'])) { + $this->description = "Key 'queries' is required in data for {$action}"; + return false; + } + if (!\is_array($value['data']['queries'])) { + $this->description = "Key 'queries' must be an array for {$action}"; + return false; + } + } + + // BulkUpdate requires both queries and data + if ($action === 'bulkUpdate') { + if (!\array_key_exists('data', $value['data'])) { + $this->description = "Key 'data' is required in data for {$action}"; + return false; + } + if (!\is_array($value['data']['data'])) { + $this->description = "Key 'data.data' must be an array for {$action}"; + return false; + } + } + + // Increment and Decrement require specific keys + if (\in_array($action, ['increment', 'decrement'])) { + if (!\array_key_exists('data', $value) || !\is_array($value['data'])) { + $this->description = "Key 'data' must be an array for {$action}"; + return false; + } + // Get the attribute key name based on type + $attributeKey = $this->type === 'tablesdb' ? 'column' : 'attribute'; + if (!\array_key_exists($attributeKey, $value['data'])) { + $this->description = "Key '{$attributeKey}' is required in data for {$action}"; + return false; + } + } + return true; } diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php index f05563123c..a0485b5ef3 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php @@ -4225,4 +4225,355 @@ trait TransactionsBase sort($remainingIds); $this->assertEquals(['doc_6', 'doc_7', 'doc_8'], $remainingIds); } + + /** + * Test validation for invalid operation inputs + */ + public function testCreateOperationsValidation(): void + { + // Create database and collection for testing + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ValidationTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'ValidationTest', + 'documentSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + $collectionId = $collection['body']['$id']; + + // Add required attribute + $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + + // Wait for attribute to be ready + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Invalid action type + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'invalidAction', + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 2: Missing required action field + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 3: Missing required databaseId field + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'collectionId' => $collectionId, + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 4: Missing documentId for create operation + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 5: Missing data for create operation + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => ID::unique() + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 6: BulkCreate with non-array data + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkCreate', + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'data' => 'not an array' + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 7: BulkUpdate with missing queries + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkUpdate', + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'data' => [ + 'data' => ['name' => 'Updated'] + ] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 8: Empty operations array + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 9: Operations not an array + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => 'not an array' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test validation for committing/rolling back transactions + */ + public function testCommitRollbackValidation(): void + { + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Missing both commit and rollback + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 2: Both commit and rollback set to true + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true, + 'rollback' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 3: Invalid transaction ID + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/invalid_id", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Test 4: Attempt to commit already committed transaction + $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test validation for non-existent resources + */ + public function testNonExistentResources(): void + { + // Create database and transaction + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ResourceTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Non-existent database + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => 'nonExistentDatabase', + 'collectionId' => 'someCollection', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Test 2: Non-existent collection + $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'collectionId' => 'nonExistentCollection', + 'documentId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + } } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index 4e264e8641..2752e2d036 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -4225,4 +4225,453 @@ trait TransactionsBase sort($remainingIds); $this->assertEquals(['row_6', 'row_7', 'row_8'], $remainingIds); } + + /** + * Test validation for invalid operation inputs + */ + public function testCreateOperationsValidation(): void + { + // Create database and table for testing + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ValidationTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'ValidationTest', + 'rowSecurity' => false, + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $table['headers']['status-code']); + $tableId = $table['body']['$id']; + + // Add required column + $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $column['headers']['status-code']); + + // Wait for column to be ready + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Invalid action type + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'invalidAction', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 2: Missing required action field + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 3: Missing required databaseId field + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'tableId' => $tableId, + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 4: Missing required tableId field + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 5: Missing rowId for create operation + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 6: Missing data for create operation + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'rowId' => ID::unique() + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 7: BulkCreate with non-array data + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkCreate', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'data' => 'not an array' + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 8: BulkUpdate with missing queries + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkUpdate', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'data' => [ + 'data' => ['name' => 'Updated'] + ] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 9: BulkUpdate with invalid query format + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkUpdate', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'data' => [ + 'queries' => 'not an array', + 'data' => ['name' => 'Updated'] + ] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 10: BulkDelete with missing queries + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'bulkDelete', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'data' => [] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 11: Increment with missing attribute + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'increment', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'rowId' => ID::unique(), + 'data' => ['value' => 1] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 12: Decrement with invalid value type + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'decrement', + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'rowId' => ID::unique(), + 'data' => [ + 'attribute' => 'counter', + 'value' => 'not a number' + ] + ] + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 13: Empty operations array + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 14: Operations not an array + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => 'not an array' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test validation for committing/rolling back transactions + */ + public function testCommitRollbackValidation(): void + { + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Missing both commit and rollback + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 2: Both commit and rollback set to true + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true, + 'rollback' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // Test 3: Invalid transaction ID + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/invalid_id", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Commit the transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Test 4: Attempt to commit already committed transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + + /** + * Test validation for non-existent resources + */ + public function testNonExistentResources(): void + { + // Create database and transaction + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'ResourceTestDatabase' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test 1: Non-existent database + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => 'nonExistentDatabase', + 'tableId' => 'someTable', + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Test 2: Non-existent table + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'action' => 'create', + 'databaseId' => $databaseId, + 'tableId' => 'nonExistentTable', + 'rowId' => ID::unique(), + 'data' => ['name' => 'Test'] + ] + ] + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + } } From 2bc90db1f6f1593b48ab06bc9beaef4a71a5fd9a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 18:56:05 +1300 Subject: [PATCH 258/385] Fix tests --- src/Appwrite/Databases/TransactionState.php | 8 ++++---- .../Http/Databases/Transactions/Operations/Create.php | 4 ++++ .../Databases/Http/Databases/Transactions/Update.php | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 85ecb41ae2..e9cb6b82e4 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -574,25 +574,25 @@ class TransactionState } break; - case Query::TYPE_GREATER_THAN: + case Query::TYPE_GREATER: if (!($docValue > $values[0])) { return false; } break; - case Query::TYPE_GREATER_THAN_EQUAL: + case Query::TYPE_GREATER_EQUAL: if (!($docValue >= $values[0])) { return false; } break; - case Query::TYPE_LESSER_THAN: + case Query::TYPE_LESSER: if (!($docValue < $values[0])) { return false; } break; - case Query::TYPE_LESSER_THAN_EQUAL: + case Query::TYPE_LESSER_EQUAL: if (!($docValue <= $values[0])) { return false; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 20cf79551c..80122a6028 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -64,6 +64,10 @@ class Create extends Action public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, array $plan): void { + if (empty($operations)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Operations array cannot be empty'); + } + $transaction = $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 19ba8f10d8..bd0c7f5582 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -129,7 +129,7 @@ class Update extends Action $totalOperations = 0; $databaseOperations = []; - $dbForProject->withTransaction(function () use ($dbForProject, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', ])); From deb3d7537539d958fe40c8ba0cbdd8e1f9b6044c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 3 Oct 2025 20:00:32 +1300 Subject: [PATCH 259/385] Explicit order asc --- src/Appwrite/Databases/TransactionState.php | 16 ++++++++++++++++ .../Http/Databases/Transactions/Update.php | 1 + 2 files changed, 17 insertions(+) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index e9cb6b82e4..31e09ccf75 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -4,6 +4,8 @@ namespace Appwrite\Databases; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception; +use Utopia\Database\Exception\Timeout; use Utopia\Database\Query; /** @@ -32,6 +34,9 @@ class TransactionState * @param string|null $transactionId Optional transaction ID * @param array $queries Optional query filters * @return Document + * @throws Exception + * @throws Exception\Query + * @throws Timeout */ public function getDocument( string $collectionId, @@ -90,6 +95,9 @@ class TransactionState * @param string|null $transactionId Optional transaction ID * @param array $queries Optional query filters * @return array Array of Document objects + * @throws Exception + * @throws Exception\Query + * @throws Timeout */ public function listDocuments( string $collectionId, @@ -147,6 +155,9 @@ class TransactionState * @param string|null $transactionId Optional transaction ID * @param array $queries Optional query filters * @return int Document count + * @throws Exception + * @throws Exception\Query + * @throws Timeout */ public function countDocuments( string $collectionId, @@ -328,6 +339,9 @@ class TransactionState * * @param string $transactionId Transaction ID * @return array State array with structure: [collectionId => [docId => ['action' => ..., 'document' => ..., 'exists' => ...]]] + * @throws Exception + * @throws Exception\Query + * @throws Timeout */ private function getTransactionState(string $transactionId): array { @@ -339,6 +353,8 @@ class TransactionState // Fetch operations ordered by sequence to replay in exact order $operations = $this->dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc(), + Query::limit(PHP_INT_MAX) ]); $state = []; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index bd0c7f5582..525c74abc7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -137,6 +137,7 @@ class Update extends Action // Fetch operations ordered by sequence by default to replay operations in exact order they were created $operations = $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc(), Query::limit(PHP_INT_MAX), ]); From ce86758cdccb9e7669233e182cdee4314243147d Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal <114170980+Priyanshuthapliyal2005@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:30:06 +0000 Subject: [PATCH 260/385] fix typo issues raise by bot --- app/config/specs/open-api3-1.8.x-client.json | 2 +- app/config/specs/open-api3-1.8.x-console.json | 4 ++-- app/config/specs/open-api3-1.8.x-server.json | 4 ++-- app/config/specs/open-api3-latest-client.json | 2 +- app/config/specs/open-api3-latest-console.json | 4 ++-- app/config/specs/open-api3-latest-server.json | 4 ++-- app/config/specs/swagger2-1.8.x-client.json | 2 +- app/config/specs/swagger2-1.8.x-console.json | 4 ++-- app/config/specs/swagger2-1.8.x-server.json | 4 ++-- app/config/specs/swagger2-latest-client.json | 2 +- app/config/specs/swagger2-latest-console.json | 4 ++-- app/config/specs/swagger2-latest-server.json | 4 ++-- .../Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php | 2 +- .../Modules/Databases/Http/TablesDB/Tables/Rows/XList.php | 2 +- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index 2038c1940a..8dc8a050f0 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -7533,7 +7533,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index e0eccd3f69..d0cc1d3161 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -37218,7 +37218,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37390,7 +37390,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 74be46d8d3..55c0d1f53e 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -27669,7 +27669,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27757,7 +27757,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 2038c1940a..8dc8a050f0 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -7533,7 +7533,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index e0eccd3f69..d0cc1d3161 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -37218,7 +37218,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -37390,7 +37390,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 74be46d8d3..55c0d1f53e 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -27669,7 +27669,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "schema": { "type": "string", @@ -27757,7 +27757,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "schema": { "type": "string", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 3003de22af..717d6ddbb2 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -7612,7 +7612,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index f222d3dff6..a276134b54 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -37249,7 +37249,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37409,7 +37409,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index db04d55cd0..e79ef98a04 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -27755,7 +27755,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27836,7 +27836,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 3003de22af..717d6ddbb2 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -7612,7 +7612,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index f222d3dff6..a276134b54 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -37249,7 +37249,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -37409,7 +37409,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index db04d55cd0..e79ef98a04 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -27755,7 +27755,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the Database service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/references\/cloud\/server-dart\/tablesDB#createTable).", "required": true, "type": "string", "x-example": "", @@ -27836,7 +27836,7 @@ }, { "name": "tableId", - "description": "Table ID. You can create a new table using the TableDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", + "description": "Table ID. You can create a new table using the TablesDB service [server integration](https:\/\/appwrite.io\/docs\/products\/databases\/tables#create-table).", "required": true, "type": "string", "x-example": "", diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php index dd97c4bdc7..ec05c92a64 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Delete.php @@ -55,7 +55,7 @@ class Delete extends IndexDelete contentType: ContentType::NONE )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).') ->param('key', '', new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php index cbbbf9520d..00953a1d04 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php @@ -49,7 +49,7 @@ class XList extends DocumentXList contentType: ContentType::JSON )) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TableDB service [server integration](https://appwrite.io/docs/products/databases/tables#create-table).') + ->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/products/databases/tables#create-table).') ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') ->inject('dbForProject') From b145c609bd81457282d9d29a693cf1ee86d11f53 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Fri, 3 Oct 2025 16:29:39 +0530 Subject: [PATCH 261/385] change error codes --- app/config/errors.php | 15 +++++++++++++++ .../Services/Account/AccountCustomClientTest.php | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/config/errors.php b/app/config/errors.php index 156af5db8f..4345884ff5 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -211,6 +211,11 @@ return [ 'description' => 'User with the requested ID could not be found.', 'code' => 404, ], + Exception::USER_EMAIL_NOT_FOUND => [ + 'name' => Exception::USER_EMAIL_NOT_FOUND, + 'description' => 'User email could not be found.', + 'code' => 400, + ], Exception::USER_EMAIL_ALREADY_EXISTS => [ 'name' => Exception::USER_EMAIL_ALREADY_EXISTS, 'description' => 'A user with the same email already exists in the current project.', @@ -312,11 +317,21 @@ return [ 'description' => 'OAuth2 provider returned some error.', 'code' => 424, ], + Exception::USER_EMAIL_NOT_VERIFIED => [ + 'name' => Exception::USER_EMAIL_NOT_VERIFIED, + 'description' => 'User email is not verified', + 'code' => 400, + ], Exception::USER_EMAIL_ALREADY_VERIFIED => [ 'name' => Exception::USER_EMAIL_ALREADY_VERIFIED, 'description' => 'User email is already verified', 'code' => 409, ], + Exception::USER_PHONE_NOT_VERIFIED => [ + 'name' => Exception::USER_PHONE_NOT_VERIFIED, + 'description' => 'User phone is not verified', + 'code' => 400, + ], Exception::USER_PHONE_ALREADY_VERIFIED => [ 'name' => Exception::USER_PHONE_ALREADY_VERIFIED, 'description' => 'User phone is already verified', diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 5cec3770f7..8ebd89c983 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1864,7 +1864,7 @@ class AccountCustomClientTest extends Scope 'url' => 'http://localhost/verification', ]); - $this->assertEquals(500, $response['body']['code']); + $this->assertEquals(400, $response['body']['code']); $this->assertEquals('user_email_not_found', $response['body']['type']); return []; From 89a47953d807643a8f66c233ba493688606f547e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 00:46:13 +1300 Subject: [PATCH 262/385] Fix tests --- src/Appwrite/Databases/TransactionState.php | 37 ++++--- .../Http/Databases/Transactions/Update.php | 96 ++++++++++--------- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 31e09ccf75..645c25bf76 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -188,6 +188,8 @@ class TransactionState $adjustedCount = $baseCount; + $filters = $this->extractFilters($queries); + // Apply transaction state changes to the count foreach ($state[$collectionId] as $docId => $docState) { if (!$docState['exists']) { @@ -197,13 +199,13 @@ class TransactionState } } elseif ($docState['action'] === 'create') { // Document was created in transaction - if ($this->documentMatchesFilters($docState['document'], $queries)) { + if ($this->documentMatchesFilters($docState['document'], $filters)) { $adjustedCount++; // New document that matches } } elseif ($docState['action'] === 'update' || $docState['action'] === 'upsert') { // Document was updated/upserted $wasInResults = isset($committedDocIds[$docId]); - $nowMatches = $this->documentMatchesFilters($docState['document'], $queries); + $nowMatches = $this->documentMatchesFilters($docState['document'], $filters); if (!$wasInResults && $nowMatches && $docState['action'] === 'upsert') { $adjustedCount++; // Upsert created new document that matches @@ -259,8 +261,10 @@ class TransactionState return; } + $filters = $this->extractFilters($queries); + foreach ($state[$collectionId] as $docId => $doc) { - if ($this->documentMatchesFilters($doc, $queries)) { + if ($this->documentMatchesFilters($doc, $filters)) { // Apply the update to the state document foreach ($updateData->getArrayCopy() as $key => $value) { if ($key !== '$id') { @@ -290,8 +294,10 @@ class TransactionState return; } + $filters = $this->extractFilters($queries); + foreach ($state[$collectionId] as $docId => $doc) { - if ($this->documentMatchesFilters($doc, $queries)) { + if ($this->documentMatchesFilters($doc, $filters)) { unset($state[$collectionId][$docId]); } } @@ -501,19 +507,17 @@ class TransactionState } /** - * Check if a document matches filter queries + * Extract only filter queries from a query array * - * @param Document $doc Document to check - * @param array $queries Query filters - * @return bool True if document matches all filters + * @param array $queries Query array + * @return array Filtered queries */ - private function documentMatchesFilters(Document $doc, array $queries): bool + private function extractFilters(array $queries): array { - // Extract filter queries $filters = []; foreach ($queries as $query) { $method = $query->getMethod(); - // Only process filter queries, not limit/offset/cursor/select + // Only process filter queries, not limit/offset/cursor/select/order if (!\in_array($method, [ Query::TYPE_LIMIT, Query::TYPE_OFFSET, @@ -526,7 +530,18 @@ class TransactionState $filters[] = $query; } } + return $filters; + } + /** + * Check if a document matches filter queries + * + * @param Document $doc Document to check + * @param array $filters Pre-filtered Query filters (use extractFilters first) + * @return bool True if document matches all filters + */ + private function documentMatchesFilters(Document $doc, array $filters): bool + { // If no filters, document matches if (empty($filters)) { return true; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 525c74abc7..c17fb7e75f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -129,22 +129,22 @@ class Update extends Action $totalOperations = 0; $databaseOperations = []; - $dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'committing', - ])); + try { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'committing', + ])); - // Fetch operations ordered by sequence by default to replay operations in exact order they were created - $operations = $dbForProject->find('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc(), - Query::limit(PHP_INT_MAX), - ]); + // Fetch operations ordered by sequence by default to replay operations in exact order they were created + $operations = $dbForProject->find('transactionLogs', [ + Query::equal('transactionInternalId', [$transaction->getSequence()]), + Query::orderAsc(), + Query::limit(PHP_INT_MAX), + ]); - // Track transaction state for cross-operation visibility - $state = []; + // Track transaction state for cross-operation visibility + $state = []; - try { foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; @@ -207,38 +207,44 @@ class Update extends Action $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); - } catch (NotFoundException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e); - } catch (DuplicateException|ConflictException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); - } catch (StructureException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); - } catch (LimitException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage()); - } catch (TransactionException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); - } catch (QueryException $e) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ - 'status' => 'failed', - ])); - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } - }); + }); + } catch (NotFoundException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e); + } catch (DuplicateException|ConflictException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); + } catch (StructureException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (LimitException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage()); + } catch (TransactionException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); + } catch (QueryException $e) { + // Transaction has been rolled back, now mark it as failed + $dbForProject->updateDocument('transactions', $transactionId, new Document([ + 'status' => 'failed', + ])); + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $totalOperations); From e93e03c248619e2153bd3761f1746f6d4f56b0c7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 01:15:53 +1300 Subject: [PATCH 263/385] Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Modules/Databases/Http/Databases/Transactions/Update.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 525c74abc7..a1f6bd2a65 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -591,8 +591,9 @@ class Update extends Action } // Use timestamp wrapper for independent operations - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data) { - $dbForProject->decreaseDocumentAttribute( + // Use timestamp wrapper for independent operations + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, attribute: $data[$this->getAttributeKey()], From 742ddc517bdcd8b37f213014a17e4a1cb6113ef2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 01:38:06 +1300 Subject: [PATCH 264/385] Update database --- composer.json | 2 +- composer.lock | 65 +++++++++++++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 1dc7288441..d51ff828a3 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "1.*", + "utopia-php/database": "dev-feat-relationship-updates as 1.5.1", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index 95288fc01d..475d66c44b 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": "7553e976312b0423cc31544abb91caec", + "content-hash": "ae57f581b8351bbabd8cf007d514e2b4", "packages": [ { "name": "adhocore/jwt", @@ -1159,16 +1159,16 @@ }, { "name": "open-telemetry/api", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2" + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/ee17d937652eca06c2341b6fadc0f74c1c1a5af2", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", "shasum": "" }, "require": { @@ -1188,7 +1188,7 @@ ] }, "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.7.x-dev" } }, "autoload": { @@ -1225,7 +1225,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/context", @@ -1415,22 +1415,22 @@ }, { "name": "open-telemetry/sdk", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89" + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/105c6e81e3d86150bd5704b00c7e4e165e957b89", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.6", + "open-telemetry/api": "^1.7", "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -1465,7 +1465,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -1508,7 +1508,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/sem-conv", @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "1.5.1", + "version": "dev-feat-relationship-updates", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9" + "reference": "d97d181d7c2ed267b0b57ba421d3364faff64f8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/56efe4daaf23abb753553acffccdcc04cd6178c9", - "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d97d181d7c2ed267b0b57ba421d3364faff64f8e", + "reference": "d97d181d7c2ed267b0b57ba421d3364faff64f8e", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.5.1" + "source": "https://github.com/utopia-php/database/tree/feat-relationship-updates" }, - "time": "2025-10-01T04:44:14+00:00" + "time": "2025-10-03T12:05:20+00:00" }, { "name": "utopia-php/detector", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd" + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07a7d6276bd684b49469ad7b9e8c3c962121c6fd", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.3" }, - "time": "2025-10-01T03:23:04+00:00" + "time": "2025-10-01T06:25:19+00:00" }, { "name": "doctrine/annotations", @@ -8515,9 +8515,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-relationship-updates", + "alias": "1.5.1", + "alias_normalized": "1.5.1.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 2c7cf7826b59205910525df1febae6fcb09c5606 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 16:43:39 +1300 Subject: [PATCH 265/385] Fix operation perm checks --- .../Http/Databases/Transactions/Create.php | 5 +- .../Transactions/Operations/Create.php | 103 ++- .../Http/Databases/Transactions/Update.php | 148 ++-- .../Http/TablesDB/Transactions/Update.php | 1 + .../Legacy/Transactions/TransactionsBase.php | 117 +-- .../TablesDB/Transactions/PermissionsBase.php | 670 ++++++++++++++++++ .../PermissionsCustomClientTest.php | 14 + .../Transactions/TransactionsBase.php | 117 +-- 8 files changed, 962 insertions(+), 213 deletions(-) create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php create mode 100644 tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index a5866ba7d1..e67c1d08d0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -9,6 +9,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Swoole\Response as SwooleResponse; @@ -57,12 +58,12 @@ class Create extends Action public function action(int $ttl, UtopiaResponse $response, Database $dbForProject): void { - $transaction = $dbForProject->createDocument('transactions', new Document([ + $transaction = Authorization::skip(fn () => $dbForProject->createDocument('transactions', new Document([ '$id' => ID::unique(), 'status' => 'pending', 'operations' => 0, 'expiresAt' => DateTime::addSeconds(new \DateTime(), $ttl), - ])); + ]))); $response ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 80122a6028..f7314fb87c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -14,6 +14,8 @@ use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Helpers\Permission; +use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; @@ -68,7 +70,7 @@ class Create extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Operations array cannot be empty'); } - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)); if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } @@ -97,18 +99,103 @@ class Create extends Action throw new Exception(Exception::USER_UNAUTHORIZED); } - $database = $databases[$operation['databaseId']] ??= $dbForProject->getDocument('databases', $operation['databaseId']); - if ($database->isEmpty()) { + $database = $databases[$operation['databaseId']] ??= Authorization::skip(fn () => $dbForProject->getDocument('databases', $operation['databaseId'])); + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = $collections[$operation[$this->getGroupId()]] ??= - $dbForProject->getDocument('database_' . $database->getSequence(), $operation[$this->getGroupId()]); + Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $operation[$this->getGroupId()])); - if ($collection->isEmpty()) { + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); } + // Check if collection has relationships for bulk operations + if (\in_array($operation['action'], ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) { + $hasRelationships = \array_filter( + $collection->getAttribute('attributes', []), + fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + ); + if ($hasRelationships) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk operations are not supported for ' . $this->getGroupId() . ' with relationship attributes'); + } + } + + // For update, upsert, delete, check document existence first + $document = null; + if (\in_array($operation['action'], ['update', 'delete', 'upsert'])) { + $documentId = $operation[$this->getResourceId()] ?? null; + if (empty($documentId)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Document ID is required for ' . $operation['action'] . ' operations'); + } + + $document = Authorization::skip(fn () => $dbForProject->getDocument( + 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), + $documentId + )); + + if ($document->isEmpty() && \in_array($operation['action'], ['update', 'delete'])) { + throw new Exception(Exception::DOCUMENT_NOT_FOUND); + } + } + + $permissionType = match ($operation['action']) { + 'create', 'bulkCreate' => Database::PERMISSION_CREATE, + 'update', 'bulkUpdate' => Database::PERMISSION_UPDATE, + 'delete', 'bulkDelete' => Database::PERMISSION_DELETE, + 'upsert', 'bulkUpsert' => ($document && !$document->isEmpty()) ? Database::PERMISSION_UPDATE : Database::PERMISSION_CREATE, + default => throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid action: ' . $operation['action']) + }; + + if (!$isAPIKey && !$isPrivilegedUser) { + $documentSecurity = $collection->getAttribute('documentSecurity', false); + $validator = new Authorization($permissionType); + $collectionValid = $validator->isValid($collection->getPermissionsByType($permissionType)); + $documentValid = false; + if ($document !== null && !$document->isEmpty() && $documentSecurity) { + if ($permissionType === Database::PERMISSION_UPDATE) { + $documentValid = $validator->isValid($document->getUpdate()); + } elseif ($permissionType === Database::PERMISSION_DELETE) { + $documentValid = $validator->isValid($document->getDelete()); + } + } + + if ($permissionType === Database::PERMISSION_CREATE || !$documentSecurity) { + if (!$collectionValid) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + } else { + if (!$collectionValid && !$documentValid) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + } + + // Users can only set permissions for roles they have + if (isset($operation['data']['$permissions'])) { + $permissions = $operation['data']['$permissions'] ?? null; + if (!\is_null($permissions)) { + $roles = Authorization::getRoles(); + foreach (Database::PERMISSIONS as $type) { + foreach ($permissions as $permission) { + $permission = Permission::parse($permission); + if ($permission->getPermission() != $type) { + continue; + } + $role = (new Role( + $permission->getRole(), + $permission->getIdentifier(), + $permission->getDimension() + ))->toString(); + if (!Authorization::isRole($role)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); + } + } + } + } + } + } + $staged[] = new Document([ '$id' => ID::unique(), 'databaseInternalId' => $database->getSequence(), @@ -121,13 +208,13 @@ class Create extends Action } $transaction = $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { - $dbForProject->createDocuments('transactionLogs', $staged); - return $dbForProject->increaseDocumentAttribute( + Authorization::skip(fn () => $dbForProject->createDocuments('transactionLogs', $staged)); + return Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute( 'transactions', $transactionId, 'operations', \count($operations) - ); + )); }); $response diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index c17fb7e75f..5c949c9227 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -14,9 +14,10 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Exception\Authorization; +use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; @@ -67,6 +68,7 @@ class Update extends Action ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) ->inject('response') ->inject('dbForProject') + ->inject('user') ->inject('transactionState') ->inject('queueForDeletes') ->inject('queueForEvents') @@ -83,6 +85,7 @@ class Update extends Action * @param bool $rollback * @param UtopiaResponse $response * @param Database $dbForProject + * @param Document $user * @param TransactionState $transactionState * @param Delete $queueForDeletes * @param Event $queueForEvents @@ -99,8 +102,9 @@ class Update extends Action * @throws Structure * @throws \Utopia\Exception */ - public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void + public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void { + if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); } @@ -108,7 +112,7 @@ class Update extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot commit and rollback at the same time'); } - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } @@ -123,6 +127,7 @@ class Update extends Action } if ($commit) { + $operations = []; // Track metrics for usage stats @@ -131,16 +136,17 @@ class Update extends Action try { $dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) { - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', - ])); + ]))); // Fetch operations ordered by sequence by default to replay operations in exact order they were created - $operations = $dbForProject->find('transactionLogs', [ + $operations = Authorization::skip(fn () => $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), Query::orderAsc(), Query::limit(PHP_INT_MAX), - ]); + ])); + // Track transaction state for cross-operation visibility $state = []; @@ -154,6 +160,15 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; + // For delete operations, fetch the document before deleting for realtime events + if ($action === 'delete' && $documentId && empty($data)) { + $doc = $dbForProject->getDocument($collectionId, $documentId); + if (!$doc->isEmpty()) { + $operation['data'] = $doc->getArrayCopy(); + $data = $operation['data']; + } + } + // Track operations for stats $totalOperations++; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + 1; @@ -197,52 +212,53 @@ class Update extends Action } } - $transaction = $dbForProject->updateDocument( + $transaction = Authorization::skip(fn () => $dbForProject->updateDocument( 'transactions', $transactionId, new Document(['status' => 'committed']) - ); + )); // Clear the transaction logs $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); }); + } catch (NotFoundException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e); } catch (DuplicateException|ConflictException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); } catch (StructureException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); } catch (LimitException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage()); } catch (TransactionException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); } catch (QueryException $e) { // Transaction has been rolled back, now mark it as failed - $dbForProject->updateDocument('transactions', $transactionId, new Document([ + Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', - ])); + ]))); throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } @@ -258,23 +274,27 @@ class Update extends Action } // Trigger realtime events for each operation + foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; + $collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}"; $action = $operation['action']; $documentId = $operation['documentId']; $data = $operation['data']; + if ($data instanceof Document) { $data = $data->getArrayCopy(); } - $database = $dbForProject->findOne('databases', [ + $database = Authorization::skip(fn () => $dbForProject->findOne('databases', [ Query::equal('$sequence', [$databaseInternalId]) - ]); - $collection = $dbForProject->findOne('database_' . $databaseInternalId, [ + ])); + + $collection = Authorization::skip(fn () => $dbForProject->findOne('database_' . $databaseInternalId, [ Query::equal('$sequence', [$collectionInternalId]) - ]); + ])); $groupId = $this->getGroupId(); $resourceId = $this->getResourceId(); @@ -290,26 +310,49 @@ class Update extends Action ->setContext($contextKey, $collection); $eventAction = ''; - $documents = []; + $documentsToTrigger = []; switch ($action) { case 'create': $eventAction = 'create'; - $documents[] = $documentId ?? $data['$id'] ?? null; + $docId = $documentId ?? $data['$id'] ?? null; + if ($docId) { + // Fetch the created document from the database + $doc = $dbForProject->getDocument($collectionId, $docId); + if (!$doc->isEmpty()) { + $documentsToTrigger[] = $doc; + } + } break; case 'update': case 'increment': case 'decrement': $eventAction = 'update'; - $documents[] = $documentId; + if ($documentId) { + // Fetch the updated document from the database + $doc = $dbForProject->getDocument($collectionId, $documentId); + if (!$doc->isEmpty()) { + $documentsToTrigger[] = $doc; + } + } break; case 'delete': $eventAction = 'delete'; - $documents[] = $documentId; + if ($documentId && !empty($data)) { + // For delete, use the fetched document data (fetched before deletion) + $documentsToTrigger[] = new Document(array_merge($data, ['$id' => $documentId])); + } break; case 'upsert': - $eventAction = 'upsert'; - $documents[] = $documentId ?? $data['$id'] ?? null; + $eventAction = 'update'; // Upsert is treated as update for events + $docId = $documentId ?? $data['$id'] ?? null; + if ($docId) { + // Fetch the upserted document from the database + $doc = $dbForProject->getDocument($collectionId, $docId); + if (!$doc->isEmpty()) { + $documentsToTrigger[] = $doc; + } + } break; case 'bulkCreate': case 'bulkUpdate': @@ -319,32 +362,43 @@ class Update extends Action } // Trigger events for each document - foreach ($documents as $docId) { - if ($docId) { - $queueForEvents - ->setParam('documentId', $docId) - ->setParam('rowId', $docId) - ->setEvent("databases.[databaseId].{$contextKey}s.[{$groupId}].{$resourcePlural}.[{$resourceId}]." . $eventAction); - $queueForRealtime->from($queueForEvents)->trigger(); - $queueForFunctions->from($queueForEvents)->trigger(); - $queueForWebhooks->from($queueForEvents)->trigger(); + $eventString = "databases.[databaseId].{$contextKey}s.[{$groupId}].{$resourcePlural}.[{$resourceId}]." . $eventAction; - $queueForEvents->reset(); - $queueForRealtime->reset(); - $queueForFunctions->reset(); - $queueForWebhooks->reset(); - } + $queueForEvents->setEvent($eventString); + + foreach ($documentsToTrigger as $doc) { + + // Add table/collection IDs to the payload for realtime channels + $payload = $doc->getArrayCopy(); + $payload['$tableId'] = $collection->getId(); + $payload['$collectionId'] = $collection->getId(); + + $queueForEvents + ->setParam('documentId', $doc->getId()) + ->setParam('rowId', $doc->getId()) + ->setPayload($payload); + + $project = $queueForEvents->getProject(); + $result = $queueForRealtime->from($queueForEvents)->trigger(); + + $queueForFunctions->from($queueForEvents)->trigger(); + $queueForWebhooks->from($queueForEvents)->trigger(); } + + $queueForEvents->reset(); + $queueForRealtime->reset(); + $queueForFunctions->reset(); + $queueForWebhooks->reset(); } } if ($rollback) { - $transaction = $dbForProject->updateDocument( + $transaction = Authorization::skip(fn () => $dbForProject->updateDocument( 'transactions', $transactionId, new Document(['status' => 'failed']) - ); + )); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index 6bdf6df7ef..72a6a9da6f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -52,6 +52,7 @@ class Update extends TransactionsUpdate ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) ->inject('response') ->inject('dbForProject') + ->inject('user') ->inject('transactionState') ->inject('queueForDeletes') ->inject('queueForEvents') diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php index a0485b5ef3..7c84295a4f 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php @@ -32,8 +32,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $response['headers']['status-code']); $this->assertArrayHasKey('$id', $response['body']); @@ -49,8 +48,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 900 ]); @@ -69,8 +67,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 30 // Below minimum ]); @@ -79,8 +76,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 4000 // Above maximum ]); @@ -109,8 +105,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -299,8 +294,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -410,8 +404,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -542,8 +535,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 60 ]); @@ -633,8 +625,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -769,14 +760,12 @@ trait TransactionsBase $txn1 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $txn2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId1 = $txn1['body']['$id']; $transactionId2 = $txn2['body']['$id']; @@ -908,8 +897,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1027,8 +1015,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1190,8 +1177,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1302,8 +1288,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1350,8 +1335,7 @@ trait TransactionsBase $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId2 = $transaction2['body']['$id']; @@ -1428,8 +1412,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1467,8 +1450,7 @@ trait TransactionsBase $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId2 = $transaction2['body']['$id']; @@ -1564,8 +1546,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -1703,8 +1684,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1815,8 +1795,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1944,8 +1923,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2047,8 +2025,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2207,8 +2184,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2331,8 +2307,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2479,8 +2454,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2616,8 +2590,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; $this->assertEquals(201, $transaction['headers']['status-code']); @@ -2860,8 +2833,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3037,8 +3009,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3219,8 +3190,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3384,8 +3354,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3547,8 +3516,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3704,8 +3672,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3861,8 +3828,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4012,8 +3978,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4163,8 +4128,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4283,8 +4247,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -4450,8 +4413,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -4532,8 +4494,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php new file mode 100644 index 0000000000..41487b2bb8 --- /dev/null +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php @@ -0,0 +1,670 @@ +client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'PermissionsTestDB' + ]); + + $this->assertEquals(201, $database['headers']['status-code']); + $this->permissionsDatabase = $database['body']['$id']; + } + + /** + * Test collection-level create permission check on staging + */ + public function testCollectionCreatePermissionDenied(): void + { + // Create a collection with no create permission for current user + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest1', + 'name' => 'Permission Test 1', + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to stage a create operation without permission, should fail + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc1', + 'data' => ['title' => 'Test Document'], + ]] + ]); + + // This should fail with 401 Unauthorized + if ($staged['headers']['status-code'] !== 401) { + echo "\nDEBUG - Actual response code: " . $staged['headers']['status-code'] . "\n"; + echo "DEBUG - Response body: " . json_encode($staged['body'], JSON_PRETTY_PRINT) . "\n"; + } + $this->assertEquals(401, $staged['headers']['status-code']); + } + + /** + * Test collection-level update permission check on staging + */ + public function testCollectionUpdatePermissionDenied(): void + { + // Create a collection with create but no update permission + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest2', + 'name' => 'Permission Test 2', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create a document first with API key + $doc = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => 'testDoc2', + 'data' => ['title' => 'Original Title'], + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to stage an update operation without permission, should fail + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'update', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc2', + 'data' => ['title' => 'Updated Title'], + ]] + ]); + + // This should fail with 401 Unauthorized + $this->assertEquals(401, $staged['headers']['status-code']); + } + + /** + * Test collection-level delete permission check on staging + */ + public function testCollectionDeletePermissionDenied(): void + { + // Create a collection with create, read but no delete permission + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest3', + 'name' => 'Permission Test 3', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + $doc = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => 'testDoc3', + 'data' => ['title' => 'To Be Deleted'], + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to stage a delete operation without permission, should fail + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'delete', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc3', + 'data' => [], + ]] + ]); + + // This should fail with 401 Unauthorized + $this->assertEquals(401, $staged['headers']['status-code']); + } + + /** + * Test document-level update permission grants access when rowSecurity is enabled + * Collection has no update permission, but document does, should succeed + */ + public function testDocumentLevelUpdatePermissionGranted(): void + { + // Create collection with rowSecurity enabled but no update permission at collection level + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest4', + 'name' => 'Permission Test 4', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + ], + 'rowSecurity' => true, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create a document with update permission at document level + $doc = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => 'testDoc4', + 'data' => ['title' => 'Protected Document'], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Stage an update, should succeed because document has update permission + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'update', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc4', + 'data' => ['title' => 'Trying to Update'], + ]] + ]); + + // This should succeed with 201 because document has update permission + $this->assertEquals(201, $staged['headers']['status-code']); + } + + /** + * Test document-level delete permission grants access when rowSecurity is enabled + * Collection has no delete permission, but document does, should succeed + */ + public function testDocumentLevelDeletePermissionGranted(): void + { + // Create collection with rowSecurity enabled but no delete permission at collection level + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest5', + 'name' => 'Permission Test 5', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'rowSecurity' => true, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create a document with delete permission at document level + $doc = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => 'testDoc5', + 'data' => ['title' => 'Can Delete Me'], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $doc['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Stage a delete should succeed because document has delete permission + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'delete', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc5', + 'data' => [], + ]] + ]); + + // This should succeed with 201 because document has DELETE permission + $this->assertEquals(201, $staged['headers']['status-code']); + } + + /** + * Test that users cannot set permissions for roles they don't have + */ + public function testCannotSetUnauthorizedRolePermissions(): void + { + // Create a collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest6', + 'name' => 'Permission Test 6', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => true, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + // Add attribute + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to stage a create with team permissions, current user is not in team + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc6', + 'data' => [ + 'title' => 'Admin Only Doc', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::team('adminTeam')), + ], + ], + ]] + ]); + + // This should fail with 401 Unauthorized, cannot set permissions for roles you don't have + $this->assertEquals(401, $staged['headers']['status-code']); + $this->assertStringContainsString('Permissions must be one of', $staged['body']['message']); + } + + /** + * Test successful staging when user has the required permissions + */ + public function testSuccessfulStagingWithProperPermissions(): void + { + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest7', + 'name' => 'Permission Test 7', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => true, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Stage a create with permissions for current user's roles, should succeed + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'testDoc7', + 'data' => [ + 'title' => 'Valid Document', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($this->getUser()['$id'])), + ], + ], + ]] + ]); + + // This should succeed + $this->assertEquals(201, $staged['headers']['status-code']); + $this->assertEquals(1, $staged['body']['operations']); + } + + /** + * Test that non-existent documents cannot be updated in transactions + */ + public function testCannotUpdateNonExistentDocument(): void + { + // Create a collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest8', + 'name' => 'Permission Test 8', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to update a document that doesn't exist - should fail + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'update', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'nonExistentDoc', + 'data' => ['title' => 'Trying to Update'], + ]] + ]); + + // This should fail with 404 Not Found + $this->assertEquals(404, $staged['headers']['status-code']); + } + + /** + * Test that non-existent documents cannot be deleted in transactions + */ + public function testCannotDeleteNonExistentDocument(): void + { + // Create a collection + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest9', + 'name' => 'Permission Test 9', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Try to delete a document that doesn't exist, should fail + $staged = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'delete', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'nonExistentDoc', + 'data' => [], + ]] + ]); + + // This should fail with 404 Not Found + $this->assertEquals(404, $staged['headers']['status-code']); + } +} diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php new file mode 100644 index 0000000000..fa33aea7b6 --- /dev/null +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php @@ -0,0 +1,14 @@ +client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $response['headers']['status-code']); $this->assertArrayHasKey('$id', $response['body']); @@ -49,8 +48,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 900 ]); @@ -69,8 +67,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 30 // Below minimum ]); @@ -79,8 +76,7 @@ trait TransactionsBase $response = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 4000 // Above maximum ]); @@ -109,8 +105,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -299,8 +294,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -410,8 +404,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -542,8 +535,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'ttl' => 60 ]); @@ -633,8 +625,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -769,14 +760,12 @@ trait TransactionsBase $txn1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $txn2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId1 = $txn1['body']['$id']; $transactionId2 = $txn2['body']['$id']; @@ -908,8 +897,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1027,8 +1015,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1190,8 +1177,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1302,8 +1288,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1350,8 +1335,7 @@ trait TransactionsBase $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId2 = $transaction2['body']['$id']; @@ -1428,8 +1412,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1467,8 +1450,7 @@ trait TransactionsBase $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId2 = $transaction2['body']['$id']; @@ -1564,8 +1546,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -1703,8 +1684,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1815,8 +1795,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -1944,8 +1923,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2047,8 +2025,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2207,8 +2184,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2331,8 +2307,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2479,8 +2454,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -2616,8 +2590,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; $this->assertEquals(201, $transaction['headers']['status-code']); @@ -2860,8 +2833,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3037,8 +3009,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3219,8 +3190,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3384,8 +3354,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3547,8 +3516,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3704,8 +3672,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -3861,8 +3828,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4012,8 +3978,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4163,8 +4128,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $transactionId = $transaction['body']['$id']; @@ -4283,8 +4247,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -4548,8 +4511,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; @@ -4630,8 +4592,7 @@ trait TransactionsBase $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); + ], $this->getHeaders())); $this->assertEquals(201, $transaction['headers']['status-code']); $transactionId = $transaction['body']['$id']; From 9edc0c16d8cb5854679e0b92a9ee7f9f4034c33b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 16:48:44 +1300 Subject: [PATCH 266/385] Realtime txn tests --- .../Realtime/RealtimeCustomClientTest.php | 458 ++++++++++++++++++ 1 file changed, 458 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 3e57c5e9bc..4e7a2a398a 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -2507,4 +2507,462 @@ class RealtimeCustomClientTest extends Scope $client->close(); } + + public function testChannelDatabaseTransaction() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('connected', $response['type']); + + /** + * Setup Database and Collection + */ + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'Transactions DB', + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Test Collection', + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + 'documentSecurity' => true, + ]); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + /** + * Test Transaction Create with Single Document + */ + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 3600 // 1 hour + ]); + + $this->assertEquals(201, $transaction['headers']['status-code'], 'Failed to create transaction: ' . json_encode($transaction['body'])); + $this->assertNotEmpty($transaction['body']['$id']); + + $transactionId = $transaction['body']['$id']; + $documentId = ID::unique(); + + $operationsResponse = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId, + 'action' => 'create', + 'data' => [ + 'name' => 'Transaction Document', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ], + ] + ] + ]); + + $this->assertEquals(201, $operationsResponse['headers']['status-code'], 'Failed to add operations: ' . json_encode($operationsResponse['body'])); + + // Commit transaction + $commitResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(200, $commitResponse['headers']['status-code'], 'Failed to commit transaction: ' . json_encode($commitResponse['body'])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains("databases.{$databaseId}.tables.{$collectionId}.rows.{$documentId}.create", $response['data']['events']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertEquals('Transaction Document', $response['data']['payload']['name']); + + /** + * Test Transaction Update + */ + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 3600 + ]); + + $transactionId = $transaction['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId, + 'action' => 'update', + 'data' => [ + 'name' => 'Updated Transaction Document', + ], + ] + ] + ]); + + $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('event', $response['type']); + $this->assertContains("databases.{$databaseId}.tables.{$collectionId}.rows.{$documentId}.update", $response['data']['events']); + $this->assertEquals('Updated Transaction Document', $response['data']['payload']['name']); + + /** + * Test Transaction Delete + */ + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 3600 + ]); + + $transactionId = $transaction['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId, + 'action' => 'delete', + ] + ] + ]); + + $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('event', $response['type']); + $this->assertContains("databases.{$databaseId}.tables.{$collectionId}.rows.{$documentId}.delete", $response['data']['events']); + + $client->close(); + } + + public function testChannelDatabaseTransactionMultipleOperations() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session + ]); + + $response = json_decode($client->receive(), true); + $this->assertEquals('connected', $response['type']); + + /** + * Setup Database and Collection + */ + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'Multi-Op DB', + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Test Collection', + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + 'documentSecurity' => true, + ]); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + /** + * Test Multiple Operations in Single Transaction + */ + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 3600 + ]); + + $transactionId = $transaction['body']['$id']; + $documentId1 = ID::unique(); + $documentId2 = ID::unique(); + $documentId3 = ID::unique(); + + $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId1, + 'action' => 'create', + 'data' => [ + 'name' => 'Doc 1', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ], + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId2, + 'action' => 'create', + 'data' => [ + 'name' => 'Doc 2', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ], + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId3, + 'action' => 'create', + 'data' => [ + 'name' => 'Doc 3', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ], + ] + ] + ]); + + $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + // Should receive 3 events, one for each document + $response1 = json_decode($client->receive(), true); + $response2 = json_decode($client->receive(), true); + $response3 = json_decode($client->receive(), true); + + $this->assertEquals('event', $response1['type']); + $this->assertEquals('event', $response2['type']); + $this->assertEquals('event', $response3['type']); + + $receivedDocIds = [ + $response1['data']['payload']['$id'], + $response2['data']['payload']['$id'], + $response3['data']['payload']['$id'], + ]; + + $this->assertContains($documentId1, $receivedDocIds); + $this->assertContains($documentId2, $receivedDocIds); + $this->assertContains($documentId3, $receivedDocIds); + + $client->close(); + } + + public function testChannelDatabaseTransactionRollback() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session + ]); + + $response = json_decode($client->receive(), true); + $this->assertEquals('connected', $response['type']); + + /** + * Setup Database and Collection + */ + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'Rollback DB', + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Test Collection', + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + 'documentSecurity' => true, + ]); + + $collectionId = $collection['body']['$id']; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + sleep(2); + + /** + * Test Transaction Rollback - Should NOT trigger realtime events + */ + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'ttl' => 3600 + ]); + + $transactionId = $transaction['body']['$id']; + $documentId = ID::unique(); + + $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $collectionId, + 'rowId' => $documentId, + 'action' => 'create', + 'data' => ['name' => 'Rollback Document'], + ] + ] + ]); + + // Rollback transaction + $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rollback' => true + ]); + + // Wait a bit to ensure no event is received + sleep(1); + + try { + $client->receive(1); // 1 second timeout + $this->fail('Should not receive any event after rollback'); + } catch (TimeoutException $e) { + // Expected - no event should be triggered + $this->assertTrue(true); + } + + $client->close(); + } } From 3e77810e13d2ec90b78627860b39cf0af292e6dd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 19:26:22 +1300 Subject: [PATCH 267/385] Fix existence check for tracked + staged ops --- src/Appwrite/Databases/TransactionState.php | 11 +- .../Http/Databases/Transactions/Create.php | 2 +- .../Transactions/Operations/Create.php | 72 ++++++----- .../Http/Databases/Transactions/Update.php | 3 +- .../Transactions/Operations/Create.php | 1 + .../TablesDB/Transactions/PermissionsBase.php | 113 ++++++++++++++++++ .../Realtime/RealtimeCustomClientTest.php | 7 +- 7 files changed, 167 insertions(+), 42 deletions(-) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 645c25bf76..2d35ce92ae 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -7,6 +7,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception; use Utopia\Database\Exception\Timeout; use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; /** * Service for managing transaction state and providing transaction-aware document operations @@ -351,17 +352,17 @@ class TransactionState */ private function getTransactionState(string $transactionId): array { - $transaction = $this->dbForProject->getDocument('transactions', $transactionId); + $transaction = Authorization::skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId)); if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') { return []; } // Fetch operations ordered by sequence to replay in exact order - $operations = $this->dbForProject->find('transactionLogs', [ + $operations = Authorization::skip(fn () => $this->dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), Query::orderAsc(), Query::limit(PHP_INT_MAX) - ]); + ])); $state = []; @@ -381,6 +382,10 @@ class TransactionState case 'create': $docId = $documentId ?? ($data['$id'] ?? null); if ($docId) { + // Ensure document has the correct $id + if (!isset($data['$id'])) { + $data['$id'] = $docId; + } $state[$collectionId][$docId] = [ 'action' => 'create', 'document' => new Document($data), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index e67c1d08d0..744ad33540 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -9,9 +9,9 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Database\Validator\Authorization; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Validator\Authorization; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\Range; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index f7314fb87c..5f6fa4f1a6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Operations; use Appwrite\Auth\Auth; +use Appwrite\Databases\TransactionState; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Databases\Http\Databases\Transactions\Action; use Appwrite\SDK\AuthType; @@ -60,11 +61,12 @@ class Create extends Action ->param('operations', [], new ArrayList(new Operation(type: 'legacy')), 'Array of staged operations.', true) ->inject('response') ->inject('dbForProject') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } - public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, array $plan): void + public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, TransactionState $transactionState, array $plan): void { if (empty($operations)) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Operations array cannot be empty'); @@ -88,7 +90,7 @@ class Create extends Action $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $databases = $collections = $staged = []; + $databases = $collections = $staged = $dependants = []; foreach ($operations as $operation) { if (!$isAPIKey && !$isPrivilegedUser && \in_array($operation['action'], [ 'bulkCreate', @@ -122,27 +124,26 @@ class Create extends Action } } - // For update, upsert, delete, check document existence first + // For update, upsert, delete, increment, decrement, check document existence first $document = null; - if (\in_array($operation['action'], ['update', 'delete', 'upsert'])) { + if (\in_array($operation['action'], ['update', 'delete', 'upsert', 'increment', 'decrement'])) { $documentId = $operation[$this->getResourceId()] ?? null; if (empty($documentId)) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Document ID is required for ' . $operation['action'] . ' operations'); } - $document = Authorization::skip(fn () => $dbForProject->getDocument( - 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - $documentId - )); + $collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + $isDependant = isset($dependants[$collectionKey][$documentId]); - if ($document->isEmpty() && \in_array($operation['action'], ['update', 'delete'])) { + $document = $transactionState->getDocument($collectionKey, $documentId, $transactionId); + if ($document->isEmpty() && !$isDependant && $operation['action'] !== 'upsert') { throw new Exception(Exception::DOCUMENT_NOT_FOUND); } } $permissionType = match ($operation['action']) { 'create', 'bulkCreate' => Database::PERMISSION_CREATE, - 'update', 'bulkUpdate' => Database::PERMISSION_UPDATE, + 'update', 'bulkUpdate', 'increment', 'decrement' => Database::PERMISSION_UPDATE, 'delete', 'bulkDelete' => Database::PERMISSION_DELETE, 'upsert', 'bulkUpsert' => ($document && !$document->isEmpty()) ? Database::PERMISSION_UPDATE : Database::PERMISSION_CREATE, default => throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid action: ' . $operation['action']) @@ -173,23 +174,21 @@ class Create extends Action // Users can only set permissions for roles they have if (isset($operation['data']['$permissions'])) { - $permissions = $operation['data']['$permissions'] ?? null; - if (!\is_null($permissions)) { - $roles = Authorization::getRoles(); - foreach (Database::PERMISSIONS as $type) { - foreach ($permissions as $permission) { - $permission = Permission::parse($permission); - if ($permission->getPermission() != $type) { - continue; - } - $role = (new Role( - $permission->getRole(), - $permission->getIdentifier(), - $permission->getDimension() - ))->toString(); - if (!Authorization::isRole($role)) { - throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); - } + $permissions = $operation['data']['$permissions']; + $roles = Authorization::getRoles(); + foreach (Database::PERMISSIONS as $type) { + foreach ($permissions as $permission) { + $permission = Permission::parse($permission); + if ($permission->getPermission() != $type) { + continue; + } + $role = (new Role( + $permission->getRole(), + $permission->getIdentifier(), + $permission->getDimension() + ))->toString(); + if (!Authorization::isRole($role)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } } @@ -205,17 +204,26 @@ class Create extends Action 'action' => $operation['action'], 'data' => $operation['data'] ?? [], ]); + + // Track create operations for dependent update/increment/decrement/delete operations in same batch + if ($operation['action'] === 'create') { + $collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + $documentId = $operation[$this->getResourceId()] ?? null; + if ($documentId) { + $dependants[$collectionKey][$documentId] = true; + } + } } - $transaction = $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { - Authorization::skip(fn () => $dbForProject->createDocuments('transactionLogs', $staged)); - return Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute( + $transaction = Authorization::skip(fn () => $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) { + $dbForProject->createDocuments('transactionLogs', $staged); + return $dbForProject->increaseDocumentAttribute( 'transactions', $transactionId, 'operations', \count($operations) - )); - }); + ); + })); $response ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 0d7ca5c252..d522c699a8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -14,16 +14,15 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; -use Utopia\Database\Validator\Authorization; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\Boolean; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index eab0fed161..d85020b2bc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -52,6 +52,7 @@ class Create extends OperationsCreate ->param('operations', [], new ArrayList(new Operation(type: 'tablesdb')), 'Array of staged operations.', true) ->inject('response') ->inject('dbForProject') + ->inject('transactionState') ->inject('plan') ->callback($this->action(...)); } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php index 41487b2bb8..9dac59e2ec 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php @@ -667,4 +667,117 @@ trait PermissionsBase // This should fail with 404 Not Found $this->assertEquals(404, $staged['headers']['status-code']); } + + /** + * Test that a document created in one batch can be updated in a subsequent batch within the same transaction + * This validates the transactionState->getDocument() fix for cross-batch dependencies + */ + public function testCanUpdateDocumentCreatedInPreviousBatch(): void + { + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest10', + 'name' => 'Permission Test 10', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + + // Batch 1: Create a document + $batch1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'crossBatchDoc', + 'data' => [ + 'title' => 'Initial Title', + ], + ]] + ]); + + $this->assertEquals(201, $batch1['headers']['status-code']); + $this->assertEquals(1, $batch1['body']['operations']); + + // Batch 2: Update the document created in batch 1 + $batch2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'update', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'crossBatchDoc', + 'data' => [ + 'title' => 'Updated Title', + ], + ]] + ]); + + // This should succeed with 201 because transactionState finds the staged document from batch 1 + $this->assertEquals(201, $batch2['headers']['status-code']); + $this->assertEquals(2, $batch2['body']['operations']); + + // Batch 3: Delete the same document + $batch3 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transaction['body']['$id'] . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'operations' => [[ + 'action' => 'delete', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'crossBatchDoc', + 'data' => [], + ]] + ]); + + // This should also succeed with 201 + $this->assertEquals(201, $batch3['headers']['status-code']); + $this->assertEquals(3, $batch3['body']['operations']); + + // Rollback to clean up + $rollback = $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transaction['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rollback' => true, + ]); + + $this->assertEquals(200, $rollback['headers']['status-code']); + } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 7152925e3a..5a61ae7dc5 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -28,7 +28,7 @@ class RealtimeCustomClientTest extends Scope $userId = $user['$id'] ?? ''; $session = $user['session'] ?? ''; - $headers = [ + $headers = [ 'origin' => 'http://localhost', 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session ]; @@ -3068,7 +3068,7 @@ class RealtimeCustomClientTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), [ 'documentId' => ID::unique(), - 'data' => [ 'name' => 'L2' ], + 'data' => ['name' => 'L2'], 'permissions' => [ Permission::read(Role::any()), Permission::update(Role::any()), @@ -3082,7 +3082,7 @@ class RealtimeCustomClientTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), [ 'documentId' => ID::unique(), - 'data' => [ 'name' => 'L1' ], + 'data' => ['name' => 'L1'], 'permissions' => [ Permission::read(Role::any()), Permission::update(Role::any()), @@ -3116,5 +3116,4 @@ class RealtimeCustomClientTest extends Scope $client->close(); } - } From 57e3e8f5f4104508dfa368083ec45efa54748194 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 21:10:50 +1300 Subject: [PATCH 268/385] Fix test scope --- .../Legacy/Transactions/TransactionsBase.php | 30 +++---------------- .../TransactionsConsoleClientTest.php | 3 +- .../TransactionsCustomClientTest.php | 3 +- .../TransactionsCustomServerTest.php | 3 +- .../Transactions/TransactionsBase.php | 30 +++---------------- 5 files changed, 14 insertions(+), 55 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php index 7c84295a4f..836f1ca966 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php @@ -1416,7 +1416,7 @@ trait TransactionsBase $transactionId = $transaction['body']['$id']; - // Try to update non-existent document + // Try to update non-existent document - should fail at staging time with early validation $response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1433,20 +1433,9 @@ trait TransactionsBase ] ]); - $this->assertEquals(201, $response['headers']['status-code']); // Operation added + $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time - // Commit should fail - $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found - - // Test delete non-existent document + // Test delete non-existent document - should also fail at staging time with early validation $transaction2 = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1470,18 +1459,7 @@ trait TransactionsBase ] ]); - $this->assertEquals(201, $response['headers']['status-code']); - - // Commit should fail - $response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found + $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time } /** diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php index 427e79fb3a..ef6e9d15d0 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\Legacy\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideConsole; -class TransactionsConsoleClientTest +class TransactionsConsoleClientTest extends Scope { use TransactionsBase; use ProjectCustom; diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php index 3ee685d778..cdc9a325dc 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\Legacy\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -class TransactionsCustomClientTest +class TransactionsCustomClientTest extends Scope { use TransactionsBase; use ProjectCustom; diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php index 425f731ef3..7b3d092128 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php @@ -3,9 +3,10 @@ namespace Tests\E2E\Services\Databases\Legacy\Transactions; use Tests\E2E\Scopes\ProjectCustom; +use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; -class TransactionsCustomServerTest +class TransactionsCustomServerTest extends Scope { use TransactionsBase; use ProjectCustom; diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index c972137cee..1ea8d0cc9a 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -1416,7 +1416,7 @@ trait TransactionsBase $transactionId = $transaction['body']['$id']; - // Try to update non-existent row + // Try to update non-existent row - should fail at staging time with early validation $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1433,20 +1433,9 @@ trait TransactionsBase ] ]); - $this->assertEquals(201, $response['headers']['status-code']); // Operation added + $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time - // Commit should fail - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found - - // Test delete non-existent row + // Test delete non-existent row - should also fail at staging time with early validation $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1470,18 +1459,7 @@ trait TransactionsBase ] ]); - $this->assertEquals(201, $response['headers']['status-code']); - - // Commit should fail - $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found + $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time } /** From 52892ee46e1d54d43e80c70016b0c0f7cb04f023 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 22:32:01 +1300 Subject: [PATCH 269/385] Upgrade for breaking changes --- .../Http/Databases/Collections/Documents/Bulk/Upsert.php | 2 +- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 2 +- src/Appwrite/Platform/Workers/StatsResources.php | 2 +- src/Appwrite/Platform/Workers/StatsUsage.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 4ce3990a38..03e4450f68 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -113,7 +113,7 @@ class Upsert extends Action try { $modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) { - return $dbForProject->createOrUpdateDocuments( + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documents, onNext: function (Document $document) use ($plan, &$upserted) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 3ac5e704e3..23fbf205dc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -243,7 +243,7 @@ class Upsert extends Action $upserted = []; try { $dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) { - return $dbForProject->createOrUpdateDocuments( + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), [$newDocument], onNext: function (Document $document) use (&$upserted) { diff --git a/src/Appwrite/Platform/Workers/StatsResources.php b/src/Appwrite/Platform/Workers/StatsResources.php index 0c8f11c07b..5ec306c5bb 100644 --- a/src/Appwrite/Platform/Workers/StatsResources.php +++ b/src/Appwrite/Platform/Workers/StatsResources.php @@ -462,7 +462,7 @@ class StatsResources extends Action }); try { - $dbForLogs->createOrUpdateDocuments( + $dbForLogs->upsertDocuments( 'stats', $this->documents, ); diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index bd34d53b00..4da63b6ae3 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -452,7 +452,7 @@ class StatsUsage extends Action return strcmp($a['time'], $b['time']); }); - $dbForProject->createOrUpdateDocumentsWithIncrease('stats', 'value', $projectStats['stats']); + $dbForProject->upsertDocumentsWithIncrease('stats', 'value', $projectStats['stats']); Console::success('Batch successfully written to DB'); } catch (Throwable $e) { Console::error('Error processing stats: ' . $e->getMessage()); @@ -532,7 +532,7 @@ class StatsUsage extends Action return strcmp($a['time'], $b['time']); }); - $dbForLogs->createOrUpdateDocumentsWithIncrease( + $dbForLogs->upsertDocumentsWithIncrease( 'stats', 'value', $this->statDocuments From 6881df6ae53e3ce529e41e0c581db8b2ca4e5929 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 22:55:07 +1300 Subject: [PATCH 270/385] Fix expectations --- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 3 +-- tests/e2e/Services/Databases/TablesDB/DatabasesBase.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index a432bc0acd..a8bd53ae3f 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4715,8 +4715,7 @@ trait DatabasesBase ], ]); - $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Invalid query: Cannot query nested attribute on: library', $documents['body']['message']); + $this->assertEquals(200, $documents['headers']['status-code']); $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/library', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index 336193f5d9..0aa4f4ba20 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4642,8 +4642,7 @@ trait DatabasesBase ], ]); - $this->assertEquals(400, $rows['headers']['status-code']); - $this->assertEquals('Invalid query: Cannot query nested attribute on: library', $rows['body']['message']); + $this->assertEquals(200, $rows['headers']['status-code']); $response = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $person['body']['$id'] . '/columns/library', array_merge([ 'content-type' => 'application/json', From 12f6b07fbf9a361c59cf90a65359a23621b34555 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 4 Oct 2025 23:20:08 +1300 Subject: [PATCH 271/385] Update database --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 475d66c44b..7446cf31a6 100644 --- a/composer.lock +++ b/composer.lock @@ -3639,12 +3639,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d97d181d7c2ed267b0b57ba421d3364faff64f8e" + "reference": "7e39c677a20a9f4ac4d84007882a460fbee206f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d97d181d7c2ed267b0b57ba421d3364faff64f8e", - "reference": "d97d181d7c2ed267b0b57ba421d3364faff64f8e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7e39c677a20a9f4ac4d84007882a460fbee206f6", + "reference": "7e39c677a20a9f4ac4d84007882a460fbee206f6", "shasum": "" }, "require": { @@ -3687,7 +3687,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-relationship-updates" }, - "time": "2025-10-03T12:05:20+00:00" + "time": "2025-10-04T10:17:19+00:00" }, { "name": "utopia-php/detector", From 5be2489e5573cbf017738e847be922cbd77a39c7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sun, 5 Oct 2025 00:06:56 +1300 Subject: [PATCH 272/385] Check query result --- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index a8bd53ae3f..bfc56567ef 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4711,11 +4711,16 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ + Query::select(['library.*'])->toString(), Query::equal('library.libraryName', ['Library 1'])->toString(), ], ]); $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(1, $documents['body']['total']); + $this->assertCount(1, $documents['body']['documents']); + $this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']); + $this->assertEquals($person1['body']['$id'], $documents['body']['documents'][0]['$id']); $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/library', array_merge([ 'content-type' => 'application/json', From ce52dba4197512d736cf0b5da78f255fc0e6f73a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sun, 5 Oct 2025 00:09:26 +1300 Subject: [PATCH 273/385] Update to release --- composer.json | 2 +- composer.lock | 69 ++++++++++++++++++++++----------------------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index d51ff828a3..9042cb088f 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-relationship-updates as 1.5.1", + "utopia-php/database": "2.*", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index 7446cf31a6..4166face48 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": "ae57f581b8351bbabd8cf007d514e2b4", + "content-hash": "773efb29b9b584b1249790e0016c823a", "packages": [ { "name": "adhocore/jwt", @@ -3293,16 +3293,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be" + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/c5e2232033b507a07f72180dc56d37e1872ee7be", - "reference": "c5e2232033b507a07f72180dc56d37e1872ee7be", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a", + "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a", "shasum": "" }, "require": { @@ -3310,7 +3310,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3338,9 +3338,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.0.0" + "source": "https://github.com/utopia-php/abuse/tree/1.0.1" }, - "time": "2025-08-13T09:12:54+00:00" + "time": "2025-09-04T12:46:54+00:00" }, { "name": "utopia-php/analytics", @@ -3390,21 +3390,21 @@ }, { "name": "utopia-php/audit", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb" + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/c0ed75f4d068f1f6c2e7149a909490d4214e72bb", - "reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", + "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -3431,9 +3431,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/1.0.0" + "source": "https://github.com/utopia-php/audit/tree/1.0.1" }, - "time": "2025-08-13T09:09:00+00:00" + "time": "2025-09-04T12:46:43+00:00" }, { "name": "utopia-php/cache", @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-relationship-updates", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7e39c677a20a9f4ac4d84007882a460fbee206f6" + "reference": "1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7e39c677a20a9f4ac4d84007882a460fbee206f6", - "reference": "7e39c677a20a9f4ac4d84007882a460fbee206f6", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3", + "reference": "1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-relationship-updates" + "source": "https://github.com/utopia-php/database/tree/2.3.0" }, - "time": "2025-10-04T10:17:19+00:00" + "time": "2025-10-04T11:07:33+00:00" }, { "name": "utopia-php/detector", @@ -4187,16 +4187,16 @@ }, { "name": "utopia-php/migration", - "version": "1.2.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093" + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/42ff497c5231f5a727d1e229419ff1d2195d8093", - "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/6fb6f8f032cd34c3c65728a55d494adeac2ff038", + "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038", "shasum": "" }, "require": { @@ -4204,7 +4204,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "1.*", + "utopia-php/database": "2.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4237,9 +4237,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.2.0" + "source": "https://github.com/utopia-php/migration/tree/1.1.0" }, - "time": "2025-09-24T10:32:24+00:00" + "time": "2025-09-10T05:45:30+00:00" }, { "name": "utopia-php/orchestration", @@ -8515,18 +8515,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-relationship-updates", - "alias": "1.5.1", - "alias_normalized": "1.5.1.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From 236585c20b6bf9435955dce5c896bf39fb995a0e Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 4 Oct 2025 18:38:35 +0530 Subject: [PATCH 274/385] fix: sanitize 5xx errors on realtime when running in production. --- app/realtime.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index bb0d4da78c..e0a776b85b 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -604,11 +604,18 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $code = 500; } + $message = $th->getMessage(); + + // sanitize 5xx errors + if ($code >= 500 && !App::isDevelopment()) { + $message = 'Error: Server Error'; + } + $response = [ 'type' => 'error', 'data' => [ 'code' => $code, - 'message' => $th->getMessage() + 'message' => $message ] ]; @@ -705,11 +712,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.'); } } catch (Throwable $th) { + $code = $th->getCode(); + $message = $th->getMessage(); + + // sanitize 5xx errors + if ($code >= 500 && !App::isDevelopment()) { + $message = 'Error: Server Error'; + } + $response = [ 'type' => 'error', 'data' => [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() + 'code' => $code, + 'message' => $message ] ]; From f5af1ee7bb0302fe0feeffb470fcfdacd4c478d2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 4 Oct 2025 21:09:19 +0530 Subject: [PATCH 275/385] update more --- app/config/platforms.php | 2 +- composer.lock | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index aeed725bf7..e5a297fb0c 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '21.0.0', + 'version' => '20.0.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 95288fc01d..fc95ef3e12 100644 --- a/composer.lock +++ b/composer.lock @@ -1159,16 +1159,16 @@ }, { "name": "open-telemetry/api", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2" + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/ee17d937652eca06c2341b6fadc0f74c1c1a5af2", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", "shasum": "" }, "require": { @@ -1188,7 +1188,7 @@ ] }, "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.7.x-dev" } }, "autoload": { @@ -1225,7 +1225,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/context", @@ -1415,22 +1415,22 @@ }, { "name": "open-telemetry/sdk", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89" + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/105c6e81e3d86150bd5704b00c7e4e165e957b89", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.6", + "open-telemetry/api": "^1.7", "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -1465,7 +1465,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -1508,7 +1508,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/sem-conv", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd" + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07a7d6276bd684b49469ad7b9e8c3c962121c6fd", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.3" }, - "time": "2025-10-01T03:23:04+00:00" + "time": "2025-10-01T06:25:19+00:00" }, { "name": "doctrine/annotations", From 81bd4cbe504c445073ee5b1c740aed11b8f32085 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sun, 5 Oct 2025 00:26:37 +0530 Subject: [PATCH 276/385] chore: update composer dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated open-telemetry/api from 1.6.0 to 1.7.0 - Updated open-telemetry/sdk from 1.8.0 to 1.9.0 - Updated utopia-php/domains from 0.8.0 to 0.8.1 - Updated appwrite/sdk-generator from 1.4.2 to 1.4.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- composer.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/composer.lock b/composer.lock index 95288fc01d..988a25c214 100644 --- a/composer.lock +++ b/composer.lock @@ -1159,16 +1159,16 @@ }, { "name": "open-telemetry/api", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2" + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/ee17d937652eca06c2341b6fadc0f74c1c1a5af2", - "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", "shasum": "" }, "require": { @@ -1188,7 +1188,7 @@ ] }, "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.7.x-dev" } }, "autoload": { @@ -1225,7 +1225,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/context", @@ -1415,22 +1415,22 @@ }, { "name": "open-telemetry/sdk", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89" + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/105c6e81e3d86150bd5704b00c7e4e165e957b89", - "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.6", + "open-telemetry/api": "^1.7", "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -1465,7 +1465,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -1508,7 +1508,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/sem-conv", @@ -3792,16 +3792,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7" + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/650463d2a1525273eb03223c48da9fb1a768bbf7", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/d5f903e93c105407da6374e411c4805b7decd8a8", + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8", "shasum": "" }, "require": { @@ -3847,9 +3847,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.0" + "source": "https://github.com/utopia-php/domains/tree/0.8.1" }, - "time": "2025-05-16T10:03:59+00:00" + "time": "2025-10-03T11:58:53+00:00" }, { "name": "utopia-php/dsn", @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd" + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07a7d6276bd684b49469ad7b9e8c3c962121c6fd", - "reference": "07a7d6276bd684b49469ad7b9e8c3c962121c6fd", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.3" }, - "time": "2025-10-01T03:23:04+00:00" + "time": "2025-10-01T06:25:19+00:00" }, { "name": "doctrine/annotations", From 0314547c95bd0ba9828367baaaacd1db4015a2f4 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sun, 5 Oct 2025 00:39:54 +0530 Subject: [PATCH 277/385] fix: permission issues --- src/Appwrite/Platform/Tasks/SDKs.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 37ed2c9f3b..63ffddf950 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -281,7 +281,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // Make sure we have a clean slate. // Otherwise, all files in this dir will be pushed, // regardless of whether they were just generated or not. - \exec('rm -rf ' . $result); + \exec('chmod -R u+w ' . $result . ' 2>/dev/null; rm -rf ' . $result); try { $sdk->generate($result); @@ -298,7 +298,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $repoBranch = $language['repoBranch'] ?? 'main'; if ($git && !empty($gitUrl)) { - // TODO: fix the temporary 2>/dev/null || true - added due to permission issues when removing files \exec('rm -rf ' . $target . ' && \ mkdir -p ' . $target . ' && \ cd ' . $target . ' && \ @@ -310,7 +309,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND git checkout ' . $gitBranch . ' || git checkout -b ' . $gitBranch . ' && \ git fetch origin ' . $gitBranch . ' || git push -u origin ' . $gitBranch . ' && \ git pull origin ' . $gitBranch . ' && \ - rm -rf ' . $target . '/* 2>/dev/null || true && \ + find . -mindepth 1 ! -path "./.git*" -delete && \ cp -r ' . $result . '/. ' . $target . '/ && \ git add . && \ git commit -m "' . $message . '" && \ @@ -377,7 +376,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } } - \exec('rm -rf ' . $target); + \exec('chmod -R u+w ' . $target . ' && rm -rf ' . $target); Console::success("Remove temp directory '{$target}' for {$language['name']} SDK"); } From 24f6da56100c29a7712cd114faafbfb7559d4dde Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 6 Oct 2025 13:47:21 +1300 Subject: [PATCH 278/385] Add assertions on tables DB --- tests/e2e/Services/Databases/TablesDB/DatabasesBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index 0aa4f4ba20..6c1c09f9d8 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4638,11 +4638,16 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ + Query::select(['library.*']), Query::equal('library.libraryName', ['Library 1'])->toString(), ], ]); $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertEquals(1, $rows['body']['total']); + $this->assertCount(1, $rows['body']['rows']); + $this->assertEquals('Library 1', $rows['body']['rows'][0]['library']['libraryName']); + $this->assertEquals($person1['body']['$id'], $rows['body']['rows'][0]['$id']); $response = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $person['body']['$id'] . '/columns/library', array_merge([ 'content-type' => 'application/json', From 2566f4d98eb30bb4d2e0ba6e02ad1af1ccda4a4f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 6 Oct 2025 14:16:01 +1300 Subject: [PATCH 279/385] Add tostring --- tests/e2e/Services/Databases/TablesDB/DatabasesBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index 6c1c09f9d8..d3aa50a99a 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4638,7 +4638,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::select(['library.*']), + Query::select(['library.*'])->toString(), Query::equal('library.libraryName', ['Library 1'])->toString(), ], ]); From 1da034d00cf254cf4300d6631c3e58b00ae6b949 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 6 Oct 2025 17:08:19 +1300 Subject: [PATCH 280/385] Update database --- composer.lock | 258 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 239 insertions(+), 19 deletions(-) diff --git a/composer.lock b/composer.lock index 4166face48..f6cbc491f6 100644 --- a/composer.lock +++ b/composer.lock @@ -959,6 +959,83 @@ }, "time": "2025-08-20T17:20:16+00:00" }, + { + "name": "mongodb/mongodb", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-mongodb": "^2.1", + "php": "^8.1", + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.32" + }, + "replace": { + "mongodb/builder": "*" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^1.2", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "6.5.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1" + }, + "time": "2025-08-13T20:50:05+00:00" + }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -1927,16 +2004,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.46", + "version": "3.0.47", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", "shasum": "" }, "require": { @@ -2017,7 +2094,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" }, "funding": [ { @@ -2033,7 +2110,7 @@ "type": "tidelift" } ], - "time": "2025-06-26T16:29:55+00:00" + "time": "2025-10-06T01:07:24+00:00" }, { "name": "psr/container", @@ -3017,6 +3094,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -3635,29 +3792,31 @@ }, { "name": "utopia-php/database", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3" + "reference": "33ca4266c7f79a037604fc889db7d79ed37d73fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3", - "reference": "1e3f40b806fbf2f146fa12f2d5e7ea09cda3dbb3", + "url": "https://api.github.com/repos/utopia-php/database/zipball/33ca4266c7f79a037604fc889db7d79ed37d73fd", + "reference": "33ca4266c7f79a037604fc889db7d79ed37d73fd", "shasum": "" }, "require": { "ext-mbstring": "*", + "ext-mongodb": "*", "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", + "utopia-php/mongo": "0.10.*", "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.*", + "laravel/pint": "*", "pcov/clobber": "2.*", "phpstan/phpstan": "1.*", "phpunit/phpunit": "9.*", @@ -3685,9 +3844,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.3.0" + "source": "https://github.com/utopia-php/database/tree/2.3.1" }, - "time": "2025-10-04T11:07:33+00:00" + "time": "2025-10-06T04:04:42+00:00" }, { "name": "utopia-php/detector", @@ -3792,16 +3951,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7" + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/650463d2a1525273eb03223c48da9fb1a768bbf7", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/d5f903e93c105407da6374e411c4805b7decd8a8", + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8", "shasum": "" }, "require": { @@ -3847,9 +4006,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.0" + "source": "https://github.com/utopia-php/domains/tree/0.8.1" }, - "time": "2025-05-16T10:03:59+00:00" + "time": "2025-10-03T11:58:53+00:00" }, { "name": "utopia-php/dsn", @@ -4241,6 +4400,67 @@ }, "time": "2025-09-10T05:45:30+00:00" }, + { + "name": "utopia-php/mongo", + "version": "0.10.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/mongo.git", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "shasum": "" + }, + "require": { + "ext-mongodb": "2.1.*", + "mongodb/mongodb": "2.1.*", + "php": ">=8.0", + "ramsey/uuid": "4.9.*" + }, + "require-dev": { + "fakerphp/faker": "1.*", + "laravel/pint": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "9.*", + "swoole/ide-helper": "5.1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Mongo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Wess", + "email": "wess@appwrite.io" + } + ], + "description": "A simple library to manage Mongo database", + "keywords": [ + "database", + "mongo", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/mongo/issues", + "source": "https://github.com/utopia-php/mongo/tree/0.10.0" + }, + "time": "2025-10-02T04:50:07+00:00" + }, { "name": "utopia-php/orchestration", "version": "0.9.1", From 02b982f3a1f434c8ab1e35e125fae69b7713ef46 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 6 Oct 2025 09:57:54 +0530 Subject: [PATCH 281/385] update: `code` fallback! --- app/realtime.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/realtime.php b/app/realtime.php index e0a776b85b..fccf5c9a20 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -713,6 +713,10 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } } catch (Throwable $th) { $code = $th->getCode(); + if (!is_int($code)) { + $code = 500; + } + $message = $th->getMessage(); // sanitize 5xx errors From b7f7fb00afb9592d1c79b5941b371d006106ded1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 6 Oct 2025 17:32:35 +1300 Subject: [PATCH 282/385] Update database --- composer.lock | 230 ++------------------------------------------------ 1 file changed, 5 insertions(+), 225 deletions(-) diff --git a/composer.lock b/composer.lock index f6cbc491f6..bad4ed32af 100644 --- a/composer.lock +++ b/composer.lock @@ -959,83 +959,6 @@ }, "time": "2025-08-20T17:20:16+00:00" }, - { - "name": "mongodb/mongodb", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "f399d24905dd42f97dfe0af9706129743ef247ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac", - "reference": "f399d24905dd42f97dfe0af9706129743ef247ac", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0", - "ext-mongodb": "^2.1", - "php": "^8.1", - "psr/log": "^1.1.4|^2|^3", - "symfony/polyfill-php85": "^1.32" - }, - "replace": { - "mongodb/builder": "*" - }, - "require-dev": { - "doctrine/coding-standard": "^12.0", - "phpunit/phpunit": "^10.5.35", - "rector/rector": "^1.2", - "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "6.5.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - }, - { - "name": "Jérôme Tamarelle", - "email": "jerome.tamarelle@mongodb.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1" - }, - "time": "2025-08-13T20:50:05+00:00" - }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -3094,86 +3017,6 @@ ], "time": "2025-07-08T02:45:35+00:00" }, - { - "name": "symfony/polyfill-php85", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-23T16:12:55+00:00" - }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -3796,27 +3639,25 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "33ca4266c7f79a037604fc889db7d79ed37d73fd" + "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/33ca4266c7f79a037604fc889db7d79ed37d73fd", - "reference": "33ca4266c7f79a037604fc889db7d79ed37d73fd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/a91e04080d7f13c35c4885dea0ffebc33cd33e1f", + "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f", "shasum": "" }, "require": { "ext-mbstring": "*", - "ext-mongodb": "*", "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.10.*", "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "*", + "laravel/pint": "1.*", "pcov/clobber": "2.*", "phpstan/phpstan": "1.*", "phpunit/phpunit": "9.*", @@ -3846,7 +3687,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/2.3.1" }, - "time": "2025-10-06T04:04:42+00:00" + "time": "2025-10-06T04:29:14+00:00" }, { "name": "utopia-php/detector", @@ -4400,67 +4241,6 @@ }, "time": "2025-09-10T05:45:30+00:00" }, - { - "name": "utopia-php/mongo", - "version": "0.10.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/mongo.git", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", - "shasum": "" - }, - "require": { - "ext-mongodb": "2.1.*", - "mongodb/mongodb": "2.1.*", - "php": ">=8.0", - "ramsey/uuid": "4.9.*" - }, - "require-dev": { - "fakerphp/faker": "1.*", - "laravel/pint": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "9.*", - "swoole/ide-helper": "5.1.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Mongo\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Wess", - "email": "wess@appwrite.io" - } - ], - "description": "A simple library to manage Mongo database", - "keywords": [ - "database", - "mongo", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.10.0" - }, - "time": "2025-10-02T04:50:07+00:00" - }, { "name": "utopia-php/orchestration", "version": "0.9.1", From 4348a176d500638b26f21ab17c26d8ba20de2b64 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 6 Oct 2025 16:44:31 +0530 Subject: [PATCH 283/385] Rename verification SDK methods to be more specific MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit renames the verification SDK methods to better reflect their purpose: - `createVerification` → `createEmailVerification` - `updateVerification` → `updateEmailVerification` The old method names are maintained for backwards compatibility and marked as deprecated (since 1.8.0) with references to the new method names. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/config/specs/open-api3-1.8.x-client.json | 116 ++++++++++++++++- app/config/specs/open-api3-1.8.x-console.json | 116 ++++++++++++++++- app/config/specs/open-api3-1.8.x-server.json | 120 +++++++++++++++++- app/config/specs/open-api3-latest-client.json | 116 ++++++++++++++++- .../specs/open-api3-latest-console.json | 116 ++++++++++++++++- app/config/specs/open-api3-latest-server.json | 120 +++++++++++++++++- app/config/specs/swagger2-1.8.x-client.json | 116 ++++++++++++++++- app/config/specs/swagger2-1.8.x-console.json | 116 ++++++++++++++++- app/config/specs/swagger2-1.8.x-server.json | 120 +++++++++++++++++- app/config/specs/swagger2-latest-client.json | 116 ++++++++++++++++- app/config/specs/swagger2-latest-console.json | 116 ++++++++++++++++- app/config/specs/swagger2-latest-server.json | 120 +++++++++++++++++- app/controllers/api/account.php | 96 ++++++++++---- 13 files changed, 1404 insertions(+), 100 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index d57b9f83b2..1ebb051018 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -3467,7 +3467,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3486,12 +3486,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3502,6 +3502,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3535,7 +3585,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3554,12 +3604,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3570,6 +3620,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index bdff664cbc..2ecd94f1f9 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -3476,7 +3476,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3495,12 +3495,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3511,6 +3511,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3543,7 +3593,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3562,12 +3612,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3578,6 +3628,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 6b766dbdee..7a2fe08274 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -3164,7 +3164,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3183,12 +3183,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3199,6 +3199,58 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] @@ -3233,7 +3285,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3252,12 +3304,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3268,6 +3320,62 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index d57b9f83b2..1ebb051018 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -3467,7 +3467,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3486,12 +3486,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3502,6 +3502,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3535,7 +3585,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3554,12 +3604,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3570,6 +3620,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index bdff664cbc..2ecd94f1f9 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -3476,7 +3476,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3495,12 +3495,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3511,6 +3511,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3543,7 +3593,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3562,12 +3612,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3578,6 +3628,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 6b766dbdee..7a2fe08274 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -3164,7 +3164,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "tags": [ "account" ], @@ -3183,12 +3183,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3199,6 +3199,58 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] @@ -3233,7 +3285,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "tags": [ "account" ], @@ -3252,12 +3304,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3268,6 +3320,62 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/components\/schemas\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index c2628533d0..60374274c1 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -3602,7 +3602,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3623,12 +3623,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3639,6 +3639,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3673,7 +3723,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3694,12 +3744,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3710,6 +3760,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index ee3702d27d..4254bf7924 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -3621,7 +3621,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3642,12 +3642,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3658,6 +3658,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3691,7 +3741,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3712,12 +3762,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3728,6 +3778,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index ff5056b35a..7b1604ebb2 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -3305,7 +3305,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3326,12 +3326,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3342,6 +3342,58 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] @@ -3377,7 +3429,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3398,12 +3450,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3414,6 +3466,62 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index c2628533d0..60374274c1 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -3602,7 +3602,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3623,12 +3623,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3639,6 +3639,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3673,7 +3723,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3694,12 +3744,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3710,6 +3760,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index ee3702d27d..4254bf7924 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -3621,7 +3621,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3642,12 +3642,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3658,6 +3658,56 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [] } @@ -3691,7 +3741,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3712,12 +3762,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3728,6 +3778,60 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [] } diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index ff5056b35a..7b1604ebb2 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -3305,7 +3305,7 @@ "\/account\/verification": { "post": { "summary": "Create email verification", - "operationId": "accountCreateVerification", + "operationId": "accountCreateEmailVerification", "consumes": [ "application\/json" ], @@ -3326,12 +3326,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "createVerification", + "method": "createEmailVerification", "group": "verification", "weight": 41, "cookies": false, "type": "", - "demo": "account\/create-verification.md", + "demo": "account\/create-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3342,6 +3342,58 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "createEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-email-verification.md" + }, + { + "name": "createVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "url" + ], + "required": [ + "url" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", + "demo": "account\/create-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.createEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] @@ -3377,7 +3429,7 @@ }, "put": { "summary": "Update email verification (confirmation)", - "operationId": "accountUpdateVerification", + "operationId": "accountUpdateEmailVerification", "consumes": [ "application\/json" ], @@ -3398,12 +3450,12 @@ }, "deprecated": false, "x-appwrite": { - "method": "updateVerification", + "method": "updateEmailVerification", "group": "verification", "weight": 42, "cookies": false, "type": "", - "demo": "account\/update-verification.md", + "demo": "account\/update-email-verification.md", "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email-verification.md", "rate-limit": 10, "rate-time": 3600, @@ -3414,6 +3466,62 @@ "server" ], "packaging": false, + "methods": [ + { + "name": "updateEmailVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-email-verification.md" + }, + { + "name": "updateVerification", + "namespace": "account", + "desc": "", + "auth": { + "Project": [], + "Session": [] + }, + "parameters": [ + "userId", + "secret" + ], + "required": [ + "userId", + "secret" + ], + "responses": [ + { + "code": 200, + "model": "#\/definitions\/token" + } + ], + "description": "Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.", + "demo": "account\/update-verification.md", + "deprecated": { + "since": "1.8.0", + "replaceWith": "account.updateEmailVerification" + } + } + ], "auth": { "Project": [], "Session": [] diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8aaa5283c4..57d9e31876 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3513,20 +3513,40 @@ App::post('/v1/account/verification') ->label('event', 'users.[userId].verification.[tokenId].create') ->label('audits.event', 'verification.create') ->label('audits.resource', 'user/{response.userId}') - ->label('sdk', new Method( - namespace: 'account', - group: 'verification', - name: 'createVerification', - description: '/docs/references/account/create-email-verification.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_TOKEN, - ) - ], - contentType: ContentType::JSON, - )) + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'verification', + name: 'createEmailVerification', + description: '/docs/references/account/create-email-verification.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_TOKEN, + ) + ], + contentType: ContentType::JSON, + ), + new Method( + namespace: 'account', + group: 'verification', + name: 'createVerification', + description: '/docs/references/account/create-email-verification.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_TOKEN, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.createEmailVerification' + ), + ) + ]) ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},userId:{userId}') ->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey']) // TODO add built-in confirm page @@ -3679,20 +3699,40 @@ App::put('/v1/account/verification') ->label('event', 'users.[userId].verification.[tokenId].update') ->label('audits.event', 'verification.update') ->label('audits.resource', 'user/{response.userId}') - ->label('sdk', new Method( - namespace: 'account', - group: 'verification', - name: 'updateVerification', - description: '/docs/references/account/update-email-verification.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_TOKEN, - ) - ], - contentType: ContentType::JSON - )) + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'verification', + name: 'updateEmailVerification', + description: '/docs/references/account/update-email-verification.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_TOKEN, + ) + ], + contentType: ContentType::JSON + ), + new Method( + namespace: 'account', + group: 'verification', + name: 'updateVerification', + description: '/docs/references/account/update-email-verification.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_TOKEN, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.updateEmailVerification' + ), + ) + ]) ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},userId:{param-userId}') ->param('userId', '', new UID(), 'User ID.') From c618144d783689fb2dcb8ca868e5924aed258432 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 6 Oct 2025 16:46:56 +0530 Subject: [PATCH 284/385] update examples --- .../java/account/create-email-verification.md | 22 +++++++++++++++++ .../java/account/update-email-verification.md | 23 ++++++++++++++++++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ .../account/create-email-verification.md | 12 ++++++++++ .../account/update-email-verification.md | 13 ++++++++++ .../account/create-email-verification.md | 11 +++++++++ .../account/update-email-verification.md | 12 ++++++++++ .../account/create-email-verification.md | 12 ++++++++++ .../account/update-email-verification.md | 13 ++++++++++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ .../account/create-email-verification.md | 11 +++++++++ .../account/update-email-verification.md | 12 ++++++++++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ .../account/create-email-verification.md | 2 ++ .../account/update-email-verification.md | 3 +++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ .../account/create-email-verification.md | 12 ++++++++++ .../account/update-email-verification.md | 13 ++++++++++ .../account/create-email-verification.md | 14 +++++++++++ .../account/update-email-verification.md | 15 ++++++++++++ .../account/create-email-verification.md | 19 +++++++++++++++ .../account/update-email-verification.md | 20 ++++++++++++++++ .../account/create-email-verification.md | 12 ++++++++++ .../account/update-email-verification.md | 13 ++++++++++ .../java/account/create-email-verification.md | 23 ++++++++++++++++++ .../java/account/update-email-verification.md | 24 +++++++++++++++++++ .../account/create-email-verification.md | 14 +++++++++++ .../account/update-email-verification.md | 15 ++++++++++++ .../account/create-email-verification.md | 12 ++++++++++ .../account/update-email-verification.md | 13 ++++++++++ .../account/create-email-verification.md | 15 ++++++++++++ .../account/update-email-verification.md | 16 +++++++++++++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ .../account/create-email-verification.md | 11 +++++++++ .../account/update-email-verification.md | 12 ++++++++++ .../account/create-email-verification.md | 14 +++++++++++ .../account/update-email-verification.md | 15 ++++++++++++ .../account/create-email-verification.md | 13 ++++++++++ .../account/update-email-verification.md | 14 +++++++++++ 44 files changed, 610 insertions(+) create mode 100644 docs/examples/1.8.x/client-android/java/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-android/java/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-apple/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-apple/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/client-web/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/client-web/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/console-cli/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/console-cli/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/console-web/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/console-web/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-dart/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-dart/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-go/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-go/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-nodejs/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-php/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-php/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-python/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-python/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/account/update-email-verification.md create mode 100644 docs/examples/1.8.x/server-swift/examples/account/create-email-verification.md create mode 100644 docs/examples/1.8.x/server-swift/examples/account/update-email-verification.md diff --git a/docs/examples/1.8.x/client-android/java/account/create-email-verification.md b/docs/examples/1.8.x/client-android/java/account/create-email-verification.md new file mode 100644 index 0000000000..dfbf059d45 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/account/create-email-verification.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.createEmailVerification( + "https://example.com", // url + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/account/update-email-verification.md b/docs/examples/1.8.x/client-android/java/account/update-email-verification.md new file mode 100644 index 0000000000..9d157c8e92 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/account/update-email-verification.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.updateEmailVerification( + "", // userId + "", // secret + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/kotlin/account/create-email-verification.md b/docs/examples/1.8.x/client-android/kotlin/account/create-email-verification.md new file mode 100644 index 0000000000..dc87901eaf --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/account/create-email-verification.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.createEmailVerification( + url = "https://example.com", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/account/update-email-verification.md b/docs/examples/1.8.x/client-android/kotlin/account/update-email-verification.md new file mode 100644 index 0000000000..9fb21d2d7c --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/account/update-email-verification.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.updateEmailVerification( + userId = "", + secret = "", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-apple/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-apple/examples/account/create-email-verification.md new file mode 100644 index 0000000000..378558ecd6 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/account/create-email-verification.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let account = Account(client) + +let token = try await account.createEmailVerification( + url: "https://example.com" +) + diff --git a/docs/examples/1.8.x/client-apple/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-apple/examples/account/update-email-verification.md new file mode 100644 index 0000000000..77ef28eb49 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/account/update-email-verification.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let account = Account(client) + +let token = try await account.updateEmailVerification( + userId: "", + secret: "" +) + diff --git a/docs/examples/1.8.x/client-flutter/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-flutter/examples/account/create-email-verification.md new file mode 100644 index 0000000000..823ea2f216 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/account/create-email-verification.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Account account = Account(client); + +Token result = await account.createEmailVerification( + url: 'https://example.com', +); diff --git a/docs/examples/1.8.x/client-flutter/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-flutter/examples/account/update-email-verification.md new file mode 100644 index 0000000000..927aadf184 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/account/update-email-verification.md @@ -0,0 +1,12 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Account account = Account(client); + +Token result = await account.updateEmailVerification( + userId: '', + secret: '', +); diff --git a/docs/examples/1.8.x/client-graphql/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-graphql/examples/account/create-email-verification.md new file mode 100644 index 0000000000..1781188527 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/account/create-email-verification.md @@ -0,0 +1,12 @@ +mutation { + accountCreateEmailVerification( + url: "https://example.com" + ) { + _id + _createdAt + userId + secret + expire + phrase + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-graphql/examples/account/update-email-verification.md new file mode 100644 index 0000000000..6386d34bfa --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/account/update-email-verification.md @@ -0,0 +1,13 @@ +mutation { + accountUpdateEmailVerification( + userId: "", + secret: "" + ) { + _id + _createdAt + userId + secret + expire + phrase + } +} diff --git a/docs/examples/1.8.x/client-react-native/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-react-native/examples/account/create-email-verification.md new file mode 100644 index 0000000000..42260501c2 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/account/create-email-verification.md @@ -0,0 +1,13 @@ +import { Client, Account } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.createEmailVerification({ + url: 'https://example.com' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-react-native/examples/account/update-email-verification.md new file mode 100644 index 0000000000..4270380d5f --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/account/update-email-verification.md @@ -0,0 +1,14 @@ +import { Client, Account } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.updateEmailVerification({ + userId: '', + secret: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md new file mode 100644 index 0000000000..ed5479dbe5 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md @@ -0,0 +1,11 @@ +POST /v1/account/verification HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "url": "https://example.com" +} diff --git a/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md new file mode 100644 index 0000000000..a4dcbf76a3 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md @@ -0,0 +1,12 @@ +PUT /v1/account/verification HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "userId": "", + "secret": "" +} diff --git a/docs/examples/1.8.x/client-web/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-web/examples/account/create-email-verification.md new file mode 100644 index 0000000000..8f93533c35 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/account/create-email-verification.md @@ -0,0 +1,13 @@ +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.createEmailVerification({ + url: 'https://example.com' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-web/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-web/examples/account/update-email-verification.md new file mode 100644 index 0000000000..4f1e03f3c6 --- /dev/null +++ b/docs/examples/1.8.x/client-web/examples/account/update-email-verification.md @@ -0,0 +1,14 @@ +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.updateEmailVerification({ + userId: '', + secret: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-cli/examples/account/create-email-verification.md b/docs/examples/1.8.x/console-cli/examples/account/create-email-verification.md new file mode 100644 index 0000000000..f9f37f2f8f --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/account/create-email-verification.md @@ -0,0 +1,2 @@ +appwrite account create-email-verification \ + --url https://example.com diff --git a/docs/examples/1.8.x/console-cli/examples/account/update-email-verification.md b/docs/examples/1.8.x/console-cli/examples/account/update-email-verification.md new file mode 100644 index 0000000000..02ff32aa57 --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/account/update-email-verification.md @@ -0,0 +1,3 @@ +appwrite account update-email-verification \ + --user-id \ + --secret diff --git a/docs/examples/1.8.x/console-web/examples/account/create-email-verification.md b/docs/examples/1.8.x/console-web/examples/account/create-email-verification.md new file mode 100644 index 0000000000..b0e52db469 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/account/create-email-verification.md @@ -0,0 +1,13 @@ +import { Client, Account } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.createEmailVerification({ + url: 'https://example.com' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/account/update-email-verification.md b/docs/examples/1.8.x/console-web/examples/account/update-email-verification.md new file mode 100644 index 0000000000..e0e09fd4ce --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/account/update-email-verification.md @@ -0,0 +1,14 @@ +import { Client, Account } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const account = new Account(client); + +const result = await account.updateEmailVerification({ + userId: '', + secret: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/server-dart/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-dart/examples/account/create-email-verification.md new file mode 100644 index 0000000000..b10173d190 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/account/create-email-verification.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setSession(''); // The user session to authenticate with + +Account account = Account(client); + +Token result = await account.createEmailVerification( + url: 'https://example.com', +); diff --git a/docs/examples/1.8.x/server-dart/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-dart/examples/account/update-email-verification.md new file mode 100644 index 0000000000..b48535a31a --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/account/update-email-verification.md @@ -0,0 +1,13 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setSession(''); // The user session to authenticate with + +Account account = Account(client); + +Token result = await account.updateEmailVerification( + userId: '', + secret: '', +); diff --git a/docs/examples/1.8.x/server-dotnet/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-dotnet/examples/account/create-email-verification.md new file mode 100644 index 0000000000..6efee895e0 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/account/create-email-verification.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetSession(""); // The user session to authenticate with + +Account account = new Account(client); + +Token result = await account.CreateEmailVerification( + url: "https://example.com" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-dotnet/examples/account/update-email-verification.md new file mode 100644 index 0000000000..a336682be3 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/account/update-email-verification.md @@ -0,0 +1,15 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetSession(""); // The user session to authenticate with + +Account account = new Account(client); + +Token result = await account.UpdateEmailVerification( + userId: "", + secret: "" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-go/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-go/examples/account/create-email-verification.md new file mode 100644 index 0000000000..d10b88e21d --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/account/create-email-verification.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/account" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithSession("") +) + +service := account.New(client) + +response, error := service.CreateEmailVerification( + "https://example.com", +) diff --git a/docs/examples/1.8.x/server-go/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-go/examples/account/update-email-verification.md new file mode 100644 index 0000000000..780405d514 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/account/update-email-verification.md @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/account" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithSession("") +) + +service := account.New(client) + +response, error := service.UpdateEmailVerification( + "", + "", +) diff --git a/docs/examples/1.8.x/server-graphql/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-graphql/examples/account/create-email-verification.md new file mode 100644 index 0000000000..1781188527 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/account/create-email-verification.md @@ -0,0 +1,12 @@ +mutation { + accountCreateEmailVerification( + url: "https://example.com" + ) { + _id + _createdAt + userId + secret + expire + phrase + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-graphql/examples/account/update-email-verification.md new file mode 100644 index 0000000000..6386d34bfa --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/account/update-email-verification.md @@ -0,0 +1,13 @@ +mutation { + accountUpdateEmailVerification( + userId: "", + secret: "" + ) { + _id + _createdAt + userId + secret + expire + phrase + } +} diff --git a/docs/examples/1.8.x/server-kotlin/java/account/create-email-verification.md b/docs/examples/1.8.x/server-kotlin/java/account/create-email-verification.md new file mode 100644 index 0000000000..de80353b29 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/account/create-email-verification.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession(""); // The user session to authenticate with + +Account account = new Account(client); + +account.createEmailVerification( + "https://example.com", // url + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/account/update-email-verification.md b/docs/examples/1.8.x/server-kotlin/java/account/update-email-verification.md new file mode 100644 index 0000000000..92dfd8d00c --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/account/update-email-verification.md @@ -0,0 +1,24 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession(""); // The user session to authenticate with + +Account account = new Account(client); + +account.updateEmailVerification( + "", // userId + "", // secret + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/account/create-email-verification.md b/docs/examples/1.8.x/server-kotlin/kotlin/account/create-email-verification.md new file mode 100644 index 0000000000..4ef178fb9f --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/account/create-email-verification.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession("") // The user session to authenticate with + +val account = Account(client) + +val response = account.createEmailVerification( + url = "https://example.com" +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/account/update-email-verification.md b/docs/examples/1.8.x/server-kotlin/kotlin/account/update-email-verification.md new file mode 100644 index 0000000000..6eb97bccc2 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/account/update-email-verification.md @@ -0,0 +1,15 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession("") // The user session to authenticate with + +val account = Account(client) + +val response = account.updateEmailVerification( + userId = "", + secret = "" +) diff --git a/docs/examples/1.8.x/server-nodejs/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-nodejs/examples/account/create-email-verification.md new file mode 100644 index 0000000000..e2aaf8015a --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/account/create-email-verification.md @@ -0,0 +1,12 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setSession(''); // The user session to authenticate with + +const account = new sdk.Account(client); + +const result = await account.createEmailVerification({ + url: 'https://example.com' +}); diff --git a/docs/examples/1.8.x/server-nodejs/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-nodejs/examples/account/update-email-verification.md new file mode 100644 index 0000000000..eb6507e332 --- /dev/null +++ b/docs/examples/1.8.x/server-nodejs/examples/account/update-email-verification.md @@ -0,0 +1,13 @@ +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setSession(''); // The user session to authenticate with + +const account = new sdk.Account(client); + +const result = await account.updateEmailVerification({ + userId: '', + secret: '' +}); diff --git a/docs/examples/1.8.x/server-php/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-php/examples/account/create-email-verification.md new file mode 100644 index 0000000000..691d6fa300 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/account/create-email-verification.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setSession(''); // The user session to authenticate with + +$account = new Account($client); + +$result = $account->createEmailVerification( + url: 'https://example.com' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-php/examples/account/update-email-verification.md new file mode 100644 index 0000000000..95cd1b5e42 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/account/update-email-verification.md @@ -0,0 +1,16 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setSession(''); // The user session to authenticate with + +$account = new Account($client); + +$result = $account->updateEmailVerification( + userId: '', + secret: '' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-python/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-python/examples/account/create-email-verification.md new file mode 100644 index 0000000000..a76a4bdb89 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/account/create-email-verification.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.account import Account + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result = account.create_email_verification( + url = 'https://example.com' +) diff --git a/docs/examples/1.8.x/server-python/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-python/examples/account/update-email-verification.md new file mode 100644 index 0000000000..5e99587e86 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/account/update-email-verification.md @@ -0,0 +1,14 @@ +from appwrite.client import Client +from appwrite.services.account import Account + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result = account.update_email_verification( + user_id = '', + secret = '' +) diff --git a/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md new file mode 100644 index 0000000000..ed5479dbe5 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md @@ -0,0 +1,11 @@ +POST /v1/account/verification HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "url": "https://example.com" +} diff --git a/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md new file mode 100644 index 0000000000..a4dcbf76a3 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md @@ -0,0 +1,12 @@ +PUT /v1/account/verification HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "userId": "", + "secret": "" +} diff --git a/docs/examples/1.8.x/server-ruby/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-ruby/examples/account/create-email-verification.md new file mode 100644 index 0000000000..11bf56b73f --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/account/create-email-verification.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_session('') # The user session to authenticate with + +account = Account.new(client) + +result = account.create_email_verification( + url: 'https://example.com' +) diff --git a/docs/examples/1.8.x/server-ruby/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-ruby/examples/account/update-email-verification.md new file mode 100644 index 0000000000..33b009a549 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/account/update-email-verification.md @@ -0,0 +1,15 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_session('') # The user session to authenticate with + +account = Account.new(client) + +result = account.update_email_verification( + user_id: '', + secret: '' +) diff --git a/docs/examples/1.8.x/server-swift/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-swift/examples/account/create-email-verification.md new file mode 100644 index 0000000000..788fd9585a --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/account/create-email-verification.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession("") // The user session to authenticate with + +let account = Account(client) + +let token = try await account.createEmailVerification( + url: "https://example.com" +) + diff --git a/docs/examples/1.8.x/server-swift/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-swift/examples/account/update-email-verification.md new file mode 100644 index 0000000000..10c8afe901 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/account/update-email-verification.md @@ -0,0 +1,14 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setSession("") // The user session to authenticate with + +let account = Account(client) + +let token = try await account.updateEmailVerification( + userId: "", + secret: "" +) + From 56076ad6497de999fee89ed0e6e89b61b51fb877 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 6 Oct 2025 19:13:06 +0530 Subject: [PATCH 285/385] update path and add alias --- app/config/specs/open-api3-1.8.x-client.json | 2 +- app/config/specs/open-api3-1.8.x-console.json | 2 +- app/config/specs/open-api3-1.8.x-server.json | 2 +- app/config/specs/open-api3-latest-client.json | 2 +- app/config/specs/open-api3-latest-console.json | 2 +- app/config/specs/open-api3-latest-server.json | 2 +- app/config/specs/swagger2-1.8.x-client.json | 2 +- app/config/specs/swagger2-1.8.x-console.json | 2 +- app/config/specs/swagger2-1.8.x-server.json | 2 +- app/config/specs/swagger2-latest-client.json | 2 +- app/config/specs/swagger2-latest-console.json | 2 +- app/config/specs/swagger2-latest-server.json | 2 +- app/controllers/api/account.php | 6 ++++-- 13 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index 1ebb051018..0ab37fa677 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -3464,7 +3464,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 2ecd94f1f9..79ec41cc63 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -3473,7 +3473,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 7a2fe08274..056010c96f 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -3161,7 +3161,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 1ebb051018..0ab37fa677 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -3464,7 +3464,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 2ecd94f1f9..79ec41cc63 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -3473,7 +3473,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 7a2fe08274..056010c96f 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -3161,7 +3161,7 @@ } } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 60374274c1..bedc855c36 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -3599,7 +3599,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 4254bf7924..0f04574558 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -3618,7 +3618,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 7b1604ebb2..d137016f47 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -3302,7 +3302,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 60374274c1..bedc855c36 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -3599,7 +3599,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 4254bf7924..0f04574558 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -3618,7 +3618,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 7b1604ebb2..d137016f47 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -3302,7 +3302,7 @@ ] } }, - "\/account\/verification": { + "\/account\/verification\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 57d9e31876..c22c0babee 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3506,7 +3506,8 @@ App::put('/v1/account/recovery') $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); -App::post('/v1/account/verification') +App::post('/v1/account/verification/email') + ->alias('/v1/account/verification') ->desc('Create email verification') ->groups(['api', 'account']) ->label('scope', 'account') @@ -3692,7 +3693,8 @@ App::post('/v1/account/verification') ->dynamic($verification, Response::MODEL_TOKEN); }); -App::put('/v1/account/verification') +App::put('/v1/account/verification/email') + ->alias('/v1/account/verification') ->desc('Update email verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') From 29dbe99840f0076562ba7fb969d368bbf3329cd4 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 6 Oct 2025 19:58:01 +0530 Subject: [PATCH 286/385] simplify config & templates --- app/config/console.php | 41 +--- .../locale/templates/email-auth-styled.tpl | 225 ------------------ .../locale/templates/email-base-styled.tpl | 5 +- app/config/locale/templates/email-base.tpl | 15 ++ .../locale/templates/email-inner-base.tpl | 2 +- .../locale/templates/email-magic-url.tpl | 2 +- .../locale/templates/email-verification.tpl | 9 - app/config/locale/translations/en.json | 3 + app/controllers/api/account.php | 78 ++---- src/Appwrite/Platform/Workers/Mails.php | 1 + tests/e2e/Services/Account/AccountBase.php | 5 - 11 files changed, 40 insertions(+), 346 deletions(-) delete mode 100644 app/config/locale/templates/email-auth-styled.tpl delete mode 100644 app/config/locale/templates/email-verification.tpl diff --git a/app/config/console.php b/app/config/console.php index 6f44368060..70bb3523e6 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -9,8 +9,6 @@ use Appwrite\Network\Platform; use Utopia\Database\Helpers\ID; use Utopia\System\System; -$localeCodes = include __DIR__ . '/locale/codes.php'; - $console = [ '$id' => ID::custom('console'), '$sequence' => ID::custom('console'), @@ -51,44 +49,7 @@ $console = [ 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') ], - 'templates' => [ - 'email.verification-en' => [ - 'subject' => 'Account Verification', - 'preview' => 'Verify your email to activate your {{project}} account.', - 'heading' => 'Verify your email to start using Appwrite Cloud', - 'hello' => 'Hello {{user}},', - 'body' => 'Thanks for signing up for Appwrite Cloud. Before you can get started, please verify your email address.', - 'footer' => 'If you didn’t create an account, you can ignore this email.', - 'buttonText' => 'Verify email', - 'thanks' => 'Thanks,', - "signature" => "{{project}} team", - ], - 'email.mfaChallenge-en' => [ - 'subject' => 'Verification Code for {{project}}', - 'preview' => 'Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.', - 'heading' => 'Complete two-step verification to use Appwrite Cloud', - 'hello' => 'Hello {{user}},', - 'body' => 'Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.', - 'thanks' => 'Thanks,', - "signature" => "{{project}} team", - ], - 'email.otpSession-en' => [ - 'subject' => 'OTP for {{project}} Login', - 'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.', - 'heading' => 'Login with OTP to use Appwrite Cloud', - 'hello' => 'Hello {{user}},', - 'body' => 'Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.', - 'thanks' => 'Thanks,', - "signature" => "{{project}} team", - ], - ], - 'customEmails' => true, + 'smtpBaseTemplate' => 'email-base-styled', ]; -foreach ($localeCodes as $localeCode) { - $console['templates']['email.verification-' . $localeCode['code']] = $console['templates']['email.verification-en']; - $console['templates']['email.mfaChallenge-' . $localeCode['code']] = $console['templates']['email.mfaChallenge-en']; - $console['templates']['email.otpSession-' . $localeCode['code']] = $console['templates']['email.otpSession-en']; -} - return $console; diff --git a/app/config/locale/templates/email-auth-styled.tpl b/app/config/locale/templates/email-auth-styled.tpl deleted file mode 100644 index a826c62e95..0000000000 --- a/app/config/locale/templates/email-auth-styled.tpl +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - - -
- {{preview}} -
{{previewWhitespace}}
-
- -
- - - - -
- Appwrite logo -
- - - - - -
-

{{heading}}

-
- - - - - -
-{{body}} -
- - - - - -
- - - - - - - -
- - - - - -
- - - - - - -
Terms -
|
-
Privacy
-

- © {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808, - Delaware, United States -

-
- - \ No newline at end of file diff --git a/app/config/locale/templates/email-base-styled.tpl b/app/config/locale/templates/email-base-styled.tpl index 16036e792c..3f3ba8dd9c 100644 --- a/app/config/locale/templates/email-base-styled.tpl +++ b/app/config/locale/templates/email-base-styled.tpl @@ -147,6 +147,7 @@ Appwrite logo @@ -155,12 +156,12 @@
-

{{subject}}

+

{{heading}}

- +
{{body}} diff --git a/app/config/locale/templates/email-base.tpl b/app/config/locale/templates/email-base.tpl index 8c94c3f63e..5153b3e4fc 100644 --- a/app/config/locale/templates/email-base.tpl +++ b/app/config/locale/templates/email-base.tpl @@ -44,6 +44,21 @@ color: currentColor; word-break: break-all; } + a.button { + box-sizing: border-box; + display: inline-block; + text-align: center; + text-decoration: none; + padding: 9px 14px; + color: #ffffff; + background-color: #2D2D31; + border: 1px solid #414146; + border-radius: 8px; + } + a.button:hover, + a.button:focus { + opacity: 0.8; + } table { width: 100%; border-spacing: 0 !important; diff --git a/app/config/locale/templates/email-inner-base.tpl b/app/config/locale/templates/email-inner-base.tpl index 677f70ce7d..4b68f224db 100644 --- a/app/config/locale/templates/email-inner-base.tpl +++ b/app/config/locale/templates/email-inner-base.tpl @@ -1,6 +1,6 @@

{{hello}}

{{body}}

-

{{buttonText}}

+

{{buttonText}}

{{footer}}

{{thanks}} diff --git a/app/config/locale/templates/email-magic-url.tpl b/app/config/locale/templates/email-magic-url.tpl index 21988c5bc1..618993e0e9 100644 --- a/app/config/locale/templates/email-magic-url.tpl +++ b/app/config/locale/templates/email-magic-url.tpl @@ -5,7 +5,7 @@
- {{buttonText}} + {{buttonText}}
diff --git a/app/config/locale/templates/email-verification.tpl b/app/config/locale/templates/email-verification.tpl deleted file mode 100644 index 4b68f224db..0000000000 --- a/app/config/locale/templates/email-verification.tpl +++ /dev/null @@ -1,9 +0,0 @@ -

{{hello}}

-

{{body}}

-

{{buttonText}}

-

{{footer}}

-

- {{thanks}} -
- {{signature}} -

\ No newline at end of file diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index e2ee20b2d7..69328e61a7 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -5,6 +5,7 @@ "emails.sender": "{{project}} Team", "emails.verification.subject": "Account Verification", "emails.verification.preview": "Verify your email to activate your {{project}} account.", + "emails.verification.heading": "Verify your email to start using Appwrite Cloud", "emails.verification.hello": "Hello {{user}},", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", @@ -33,6 +34,7 @@ "emails.sessionAlert.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", "emails.otpSession.preview": "Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.", + "emails.otpSession.heading": "Login with OTP to use Appwrite Cloud", "emails.otpSession.hello": "Hello {{user}},", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", @@ -41,6 +43,7 @@ "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", "emails.mfaChallenge.preview": "Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.", + "emails.mfaChallenge.heading": "Complete two-step verification to use Appwrite Cloud", "emails.mfaChallenge.hello": "Hello {{user}},", "emails.mfaChallenge.description": "Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3aa9678a72..e98376dfc7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2296,11 +2296,11 @@ App::post('/v1/account/tokens/email') $subject = $locale->getText("emails.otpSession.subject"); $preview = $locale->getText("emails.otpSession.preview"); - $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; + $heading = $locale->getText("emails.otpSession.heading"); - $customEmails = $project->getAttribute('customEmails', false); - $bodyTemplate = ''; - $heading = ''; + $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; + $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $detector = new Detector($request->getUserAgent('UNKNOWN')); $agentOs = $detector->getOS(); @@ -2369,23 +2369,6 @@ App::post('/v1/account/tokens/email') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } elseif ($customEmails && !empty($customTemplate)) { - $subject = $customTemplate['subject']; - $preview = $customTemplate['preview']; - $heading = $customTemplate['heading']; - - $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-otp.tpl'); - $message - ->setParam('{{hello}}', $customTemplate['hello']) - ->setParam('{{description}}', $customTemplate['body'], escapeHtml: false) - ->setParam('{{thanks}}', $customTemplate['thanks']) - ->setParam('{{signature}}', $customTemplate['signature']) - ->setParam('{{clientInfo}}', '') - ->setParam('{{securityPhrase}}', '') - ->setParam('{{securityPhraseDividerDisplay}}', 'none'); - - $body = $message->render(); - $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -2402,7 +2385,7 @@ App::post('/v1/account/tokens/email') 'team' => '', ]; - if ($customEmails && !empty($customTemplate)) { + if ($smtpBaseTemplate === 'email-base-styled') { $emailVariables = array_merge($emailVariables, [ 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, @@ -3624,7 +3607,11 @@ App::post('/v1/account/verification') $body = $locale->getText("emails.verification.body"); $preview = $locale->getText("emails.verification.preview"); $subject = $locale->getText("emails.verification.subject"); + $heading = $locale->getText("emails.verification.heading"); + $customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? []; + $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message @@ -3644,10 +3631,6 @@ App::post('/v1/account/verification') $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); $replyTo = ""; - $customEmails = $project->getAttribute('customEmails', false); - $bodyTemplate = ''; - $heading = ''; - if ($smtpEnabled) { if (!empty($smtp['senderEmail'])) { $senderEmail = $smtp['senderEmail']; @@ -3685,22 +3668,6 @@ App::post('/v1/account/verification') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } elseif ($customEmails && !empty($customTemplate)) { - $subject = $customTemplate['subject']; - $preview = $customTemplate['preview']; - $heading = $customTemplate['heading']; - - $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-verification.tpl'); - $message - ->setParam('{{hello}}', $customTemplate['hello']) - ->setParam('{{body}}', $customTemplate['body'], escapeHtml: false) - ->setParam('{{buttonText}}', $customTemplate['buttonText']) - ->setParam('{{footer}}', $customTemplate['footer']) - ->setParam('{{thanks}}', $customTemplate['thanks']) - ->setParam('{{signature}}', $customTemplate['signature']); - - $body = $message->render(); - $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -3713,7 +3680,7 @@ App::post('/v1/account/verification') 'team' => '', ]; - if ($customEmails && !empty($customTemplate)) { + if ($smtpBaseTemplate === 'email-base-styled') { $emailVariables = array_merge($emailVariables, [ 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, @@ -4736,7 +4703,11 @@ App::post('/v1/account/mfa/challenge') $subject = $locale->getText("emails.mfaChallenge.subject"); $preview = $locale->getText("emails.mfaChallenge.preview"); + $heading = $locale->getText("emails.mfaChallenge.heading"); + $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; + $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $detector = new Detector($request->getUserAgent('UNKNOWN')); $agentOs = $detector->getOS(); @@ -4760,10 +4731,6 @@ App::post('/v1/account/mfa/challenge') $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); $replyTo = ""; - $customEmails = $project->getAttribute('customEmails', false); - $bodyTemplate = ''; - $heading = ''; - if ($smtpEnabled) { if (!empty($smtp['senderEmail'])) { $senderEmail = $smtp['senderEmail']; @@ -4801,21 +4768,6 @@ App::post('/v1/account/mfa/challenge') ->setSmtpReplyTo($replyTo) ->setSmtpSenderEmail($senderEmail) ->setSmtpSenderName($senderName); - } elseif ($customEmails && !empty($customTemplate)) { - $subject = $customTemplate['subject']; - $preview = $customTemplate['preview']; - $heading = $customTemplate['heading']; - - $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl'); - $message - ->setParam('{{hello}}', $customTemplate['hello']) - ->setParam('{{description}}', $customTemplate['body'], escapeHtml: false) - ->setParam('{{thanks}}', $customTemplate['thanks']) - ->setParam('{{signature}}', $customTemplate['signature']) - ->setParam('{{clientInfo}}', ''); - - $body = $message->render(); - $bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl'; } $emailVariables = [ @@ -4829,7 +4781,7 @@ App::post('/v1/account/mfa/challenge') 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', ]; - if ($customEmails && !empty($customTemplate)) { + if ($smtpBaseTemplate === 'email-base-styled') { $emailVariables = array_merge($emailVariables, [ 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index 117b689863..efca484ebf 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -82,6 +82,7 @@ class Mails extends Action $preview = $payload['preview'] ?? ''; $variables['subject'] = $subject; + $variables['heading'] = $variables['heading'] ?? $subject; $variables['year'] = date("Y"); $attachment = $payload['attachment'] ?? []; diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 46283e8f86..b2f85637a8 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -192,11 +192,6 @@ trait AccountBase $this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']); } - // TODO: Remove this once OTP login is supported for Console. - if ($isConsoleProject) { - return; - } - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', From eeebea1dbb89a8bd36b4c36ab855214ca104719f Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Mon, 6 Oct 2025 20:13:36 +0530 Subject: [PATCH 287/385] added both collection and table id in the realtime --- src/Appwrite/Messaging/Adapter/Realtime.php | 12 ++++++++---- .../Services/Realtime/RealtimeCustomClientTest.php | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 8b5e121dcd..35b8089668 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -312,13 +312,17 @@ class Realtime extends MessagingAdapter throw new \Exception('Collection or the Table needs to be passed to Realtime for Document/Row events in the Database.'); } + $tableId = $payload->getAttribute('$tableId', ''); + $collectionId = $payload->getAttribute('$collectionId', ''); + $resourceId = $tableId ?: $collectionId; + $channels[] = 'rows'; - $channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$tableId') . '.rows'; - $channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$tableId') . '.rows.' . $payload->getId(); + $channels[] = 'databases.' . $database->getId() . '.tables.' . $resourceId . '.rows'; + $channels[] = 'databases.' . $database->getId() . '.tables.' . $resourceId . '.rows.' . $payload->getId(); $channels[] = 'documents'; - $channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents'; - $channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents.' . $payload->getId(); + $channels[] = 'databases.' . $database->getId() . '.collections.' . $resourceId . '.documents'; + $channels[] = 'databases.' . $database->getId() . '.collections.' . $resourceId . '.documents.' . $payload->getId(); $roles = $collection->getAttribute('documentSecurity', false) ? \array_merge($collection->getRead(), $payload->getRead()) diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index ef39908658..e44c1170a3 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -787,6 +787,8 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('databases.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); $this->assertContains('databases.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); + $this->assertContains('databases.' . $databaseId . '.tables.' . $actorsId . '.rows.' . $documentId, $response['data']['channels']); + $this->assertContains('databases.' . $databaseId . '.tables.' . $actorsId . '.rows', $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}.create", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.create", $response['data']['events']); @@ -831,6 +833,8 @@ class RealtimeCustomClientTest extends Scope $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}.update", $response['data']['events']); + $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows", $response['data']['channels']); + $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows.{$documentId}.update", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.update", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); @@ -884,6 +888,8 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows.{$documentId}", $response['data']['channels']); + $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows", $response['data']['channels']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}.delete", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.delete", $response['data']['events']); From caf18372ce36eb777fdb894b0c0af368e27ed7bc Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 6 Oct 2025 21:52:18 +0530 Subject: [PATCH 288/385] feedback --- app/config/console.php | 2 +- .../locale/templates/email-base-styled.tpl | 8 +++++ app/config/locale/templates/email-base.tpl | 7 +++- app/config/locale/templates/email-otp.tpl | 4 +-- app/config/locale/translations/en.json | 6 ++-- app/controllers/api/account.php | 35 ++++++++++++++----- app/init/constants.php | 1 + 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/config/console.php b/app/config/console.php index 70bb3523e6..f8f68a8039 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -49,7 +49,7 @@ $console = [ 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') ], - 'smtpBaseTemplate' => 'email-base-styled', + 'smtpBaseTemplate' => APP_BRANDED_EMAIL_BASE_TEMPLATE, ]; return $console; diff --git a/app/config/locale/templates/email-base-styled.tpl b/app/config/locale/templates/email-base-styled.tpl index 3f3ba8dd9c..37ca630d43 100644 --- a/app/config/locale/templates/email-base-styled.tpl +++ b/app/config/locale/templates/email-base-styled.tpl @@ -131,6 +131,14 @@ .social-icon > img { margin: auto; } + p.security-phrase:not(:empty) { + opacity: 0.7; + margin: 0; + padding: 0; + margin-top: 32px; + padding-top: 32px; + border-top: 1px solid #e8e9f0; + } diff --git a/app/config/locale/templates/email-base.tpl b/app/config/locale/templates/email-base.tpl index 5153b3e4fc..de632d7838 100644 --- a/app/config/locale/templates/email-base.tpl +++ b/app/config/locale/templates/email-base.tpl @@ -109,10 +109,15 @@ h* { font-family: 'Poppins', sans-serif; } - p { margin-bottom: 10px; } + p.security-phrase:not(:empty) { + opacity: 0.7; + margin-top: 32px; + padding-top: 32px; + border-top: 1px solid #e8e9f0; + } diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index e18a4ce725..cfcdb8f7af 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -15,6 +15,4 @@

{{thanks}}

{{signature}}

-
- -

{{securityPhrase}}

\ No newline at end of file +

{{securityPhrase}}

\ No newline at end of file diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 69328e61a7..0500c4c668 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -5,7 +5,7 @@ "emails.sender": "{{project}} Team", "emails.verification.subject": "Account Verification", "emails.verification.preview": "Verify your email to activate your {{project}} account.", - "emails.verification.heading": "Verify your email to start using Appwrite Cloud", + "emails.verification.heading": "Verify your email to start using {{project}}", "emails.verification.hello": "Hello {{user}},", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", @@ -34,7 +34,7 @@ "emails.sessionAlert.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", "emails.otpSession.preview": "Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.", - "emails.otpSession.heading": "Login with OTP to use Appwrite Cloud", + "emails.otpSession.heading": "Login with OTP to use {{project}}", "emails.otpSession.hello": "Hello {{user}},", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", @@ -43,7 +43,7 @@ "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", "emails.mfaChallenge.preview": "Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.", - "emails.mfaChallenge.heading": "Complete two-step verification to use Appwrite Cloud", + "emails.mfaChallenge.heading": "Complete two-step verification to use {{project}}", "emails.mfaChallenge.hello": "Hello {{user}},", "emails.mfaChallenge.description": "Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e98376dfc7..af9a772b17 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -69,6 +69,12 @@ use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/console/auth/oauth2/success'; $oauthDefaultFailure = '/console/auth/oauth2/failure'; +function containsDirectoryTraversal(string $path) +{ + // Matches '../', './', '/..', or absolute paths starting with '/' + return preg_match('/(\.\.\/|\.\/|\/\.\.|\/)/', $path); +} + function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails) { $subject = $locale->getText("emails.sessionAlert.subject"); @@ -2300,6 +2306,11 @@ App::post('/v1/account/tokens/email') $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + + if (containsDirectoryTraversal($smtpBaseTemplate)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); + } + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $detector = new Detector($request->getUserAgent('UNKNOWN')); @@ -2317,10 +2328,8 @@ App::post('/v1/account/tokens/email') if (!empty($phrase)) { $message->setParam('{{securityPhrase}}', $locale->getText("emails.otpSession.securityPhrase")); - $message->setParam('{{securityPhraseDividerDisplay}}', 'block'); } else { $message->setParam('{{securityPhrase}}', ''); - $message->setParam('{{securityPhraseDividerDisplay}}', 'none'); } $body = $message->render(); @@ -2372,6 +2381,7 @@ App::post('/v1/account/tokens/email') } $emailVariables = [ + 'heading' => $heading, 'direction' => $locale->getText('settings.direction'), // {{user}}, {{project}} and {{otp}} are required in the templates 'user' => $user->getAttribute('name'), @@ -2385,9 +2395,8 @@ App::post('/v1/account/tokens/email') 'team' => '', ]; - if ($smtpBaseTemplate === 'email-base-styled') { + if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { $emailVariables = array_merge($emailVariables, [ - 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, 'logoUrl' => APP_EMAIL_LOGO_URL, 'twitterUrl' => APP_SOCIAL_TWITTER, @@ -3611,6 +3620,11 @@ App::post('/v1/account/verification') $customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + + if (containsDirectoryTraversal($smtpBaseTemplate)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); + } + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); @@ -3671,6 +3685,7 @@ App::post('/v1/account/verification') } $emailVariables = [ + 'heading' => $heading, 'direction' => $locale->getText('settings.direction'), // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $user->getAttribute('name'), @@ -3680,9 +3695,8 @@ App::post('/v1/account/verification') 'team' => '', ]; - if ($smtpBaseTemplate === 'email-base-styled') { + if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { $emailVariables = array_merge($emailVariables, [ - 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, 'logoUrl' => APP_EMAIL_LOGO_URL, 'twitterUrl' => APP_SOCIAL_TWITTER, @@ -4707,6 +4721,11 @@ App::post('/v1/account/mfa/challenge') $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + + if (containsDirectoryTraversal($smtpBaseTemplate)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); + } + $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; $detector = new Detector($request->getUserAgent('UNKNOWN')); @@ -4771,6 +4790,7 @@ App::post('/v1/account/mfa/challenge') } $emailVariables = [ + 'heading' => $heading, 'direction' => $locale->getText('settings.direction'), // {{user}}, {{project}} and {{otp}} are required in the templates 'user' => $user->getAttribute('name'), @@ -4781,9 +4801,8 @@ App::post('/v1/account/mfa/challenge') 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', ]; - if ($smtpBaseTemplate === 'email-base-styled') { + if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { $emailVariables = array_merge($emailVariables, [ - 'heading' => $heading, 'accentColor' => APP_EMAIL_ACCENT_COLOR, 'logoUrl' => APP_EMAIL_LOGO_URL, 'twitterUrl' => APP_SOCIAL_TWITTER, diff --git a/app/init/constants.php b/app/init/constants.php index 28cf8a4052..16ddcf5551 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -85,6 +85,7 @@ const APP_PLATFORM_CLIENT = 'client'; const APP_PLATFORM_CONSOLE = 'console'; const APP_VCS_GITHUB_USERNAME = 'Appwrite'; const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; +const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled'; // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; From cb70a6e2c8431bc66b26c4e5a8b8e4e0bb1cd0e4 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 6 Oct 2025 22:14:29 +0530 Subject: [PATCH 289/385] Add project name in email subject --- app/config/locale/translations/en.json | 4 ++-- tests/e2e/Services/Account/AccountCustomClientTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index e2ee20b2d7..36c98c4656 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -3,7 +3,7 @@ "settings.locale": "en", "settings.direction": "ltr", "emails.sender": "{{project}} Team", - "emails.verification.subject": "Account Verification", + "emails.verification.subject": "Account Verification for {{project}}", "emails.verification.preview": "Verify your email to activate your {{project}} account.", "emails.verification.hello": "Hello {{user}},", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", @@ -46,7 +46,7 @@ "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", "emails.mfaChallenge.thanks": "Thanks,", "emails.mfaChallenge.signature": "{{project}} team", - "emails.recovery.subject": "Password Reset", + "emails.recovery.subject": "Password Reset for {{project}}", "emails.recovery.preview": "Reset your {{project}} password using the link.", "emails.recovery.hello": "Hello {{user}},", "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 8322508cf6..b1b4f47b34 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -924,7 +924,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($email, $lastEmail['to'][0]['address']); $this->assertEquals($name, $lastEmail['to'][0]['name']); - $this->assertEquals('Account Verification', $lastEmail['subject']); + $this->assertEquals('Account Verification for ' . $this->getProject()['name'], $lastEmail['subject']); $this->assertStringContainsStringIgnoringCase('Verify your email to activate your ' . $this->getProject()['name'] . ' account.', $lastEmail['text']); $tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']); @@ -1228,7 +1228,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($email, $lastEmail['to'][0]['address']); $this->assertEquals($name, $lastEmail['to'][0]['name']); - $this->assertEquals('Password Reset', $lastEmail['subject']); + $this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']); $this->assertStringContainsStringIgnoringCase('Reset your ' . $this->getProject()['name'] . ' password using the link.', $lastEmail['text']); From 99c0ccacf1fd0971d020636220f42580a8a1b532 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 7 Oct 2025 16:11:18 +1300 Subject: [PATCH 290/385] Update database --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index bad4ed32af..d4f1f85151 100644 --- a/composer.lock +++ b/composer.lock @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f" + "reference": "35c978ddd20b649d119296094686d3cc9fcf161f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/a91e04080d7f13c35c4885dea0ffebc33cd33e1f", - "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/35c978ddd20b649d119296094686d3cc9fcf161f", + "reference": "35c978ddd20b649d119296094686d3cc9fcf161f", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.3.1" + "source": "https://github.com/utopia-php/database/tree/2.3.2" }, - "time": "2025-10-06T04:29:14+00:00" + "time": "2025-10-07T03:09:32+00:00" }, { "name": "utopia-php/detector", From 52ac68b511603a26e8debe7c2204aa8bf394a709 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 7 Oct 2025 16:12:20 +1300 Subject: [PATCH 291/385] Trigger CI From 024d13c1b244d6cbcb6152c26c68a805807e1ab7 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 7 Oct 2025 09:58:39 +0530 Subject: [PATCH 292/385] update naming --- app/config/specs/open-api3-1.8.x-client.json | 4 ++-- app/config/specs/open-api3-1.8.x-console.json | 4 ++-- app/config/specs/open-api3-1.8.x-server.json | 4 ++-- app/config/specs/open-api3-latest-client.json | 4 ++-- app/config/specs/open-api3-latest-console.json | 4 ++-- app/config/specs/open-api3-latest-server.json | 4 ++-- app/config/specs/swagger2-1.8.x-client.json | 4 ++-- app/config/specs/swagger2-1.8.x-console.json | 4 ++-- app/config/specs/swagger2-1.8.x-server.json | 4 ++-- app/config/specs/swagger2-latest-client.json | 4 ++-- app/config/specs/swagger2-latest-console.json | 4 ++-- app/config/specs/swagger2-latest-server.json | 4 ++-- app/controllers/api/account.php | 10 ++++++---- 13 files changed, 30 insertions(+), 28 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index 0ab37fa677..1b0b97ba7c 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -3464,7 +3464,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3712,7 +3712,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 79ec41cc63..582ba3e785 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -3473,7 +3473,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3719,7 +3719,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 056010c96f..d6e1ad432f 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -3161,7 +3161,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3415,7 +3415,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 0ab37fa677..1b0b97ba7c 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -3464,7 +3464,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3712,7 +3712,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 79ec41cc63..582ba3e785 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -3473,7 +3473,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3719,7 +3719,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 056010c96f..d6e1ad432f 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -3161,7 +3161,7 @@ } } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3415,7 +3415,7 @@ } } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index bedc855c36..15a66ce85f 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -3599,7 +3599,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3854,7 +3854,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 0f04574558..d4502a5ef9 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -3618,7 +3618,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3871,7 +3871,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index d137016f47..42dbd7c4f7 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -3302,7 +3302,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3563,7 +3563,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index bedc855c36..15a66ce85f 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -3599,7 +3599,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3854,7 +3854,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 0f04574558..d4502a5ef9 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -3618,7 +3618,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3871,7 +3871,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index d137016f47..42dbd7c4f7 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -3302,7 +3302,7 @@ ] } }, - "\/account\/verification\/email": { + "\/account\/verifications\/email": { "post": { "summary": "Create email verification", "operationId": "accountCreateEmailVerification", @@ -3563,7 +3563,7 @@ ] } }, - "\/account\/verification\/phone": { + "\/account\/verifications\/phone": { "post": { "summary": "Create phone verification", "operationId": "accountCreatePhoneVerification", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c22c0babee..b23e2489fa 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3506,7 +3506,7 @@ App::put('/v1/account/recovery') $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); -App::post('/v1/account/verification/email') +App::post('/v1/account/verifications/email') ->alias('/v1/account/verification') ->desc('Create email verification') ->groups(['api', 'account']) @@ -3693,7 +3693,7 @@ App::post('/v1/account/verification/email') ->dynamic($verification, Response::MODEL_TOKEN); }); -App::put('/v1/account/verification/email') +App::put('/v1/account/verifications/email') ->alias('/v1/account/verification') ->desc('Update email verification (confirmation)') ->groups(['api', 'account']) @@ -3781,7 +3781,8 @@ App::put('/v1/account/verification/email') $response->dynamic($verification, Response::MODEL_TOKEN); }); -App::post('/v1/account/verification/phone') +App::post('/v1/account/verifications/phone') + ->alias('/v1/account/verification/phone') ->desc('Create phone verification') ->groups(['api', 'account', 'auth']) ->label('scope', 'account') @@ -3930,7 +3931,8 @@ App::post('/v1/account/verification/phone') ->dynamic($verification, Response::MODEL_TOKEN); }); -App::put('/v1/account/verification/phone') +App::put('/v1/account/verifications/phone') + ->alias('/v1/account/verification/phone') ->desc('Update phone verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') From 61b073a6a227be9ff4626718a183532440010a06 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 7 Oct 2025 18:53:38 +1300 Subject: [PATCH 293/385] Clean up --- composer.lock | 24 ++-- src/Appwrite/Databases/TransactionState.php | 29 ++--- .../Transactions/Operations/Create.php | 1 - .../Http/Databases/Transactions/Update.php | 106 +++++++----------- 4 files changed, 71 insertions(+), 89 deletions(-) diff --git a/composer.lock b/composer.lock index 8450c9df98..bad4ed32af 100644 --- a/composer.lock +++ b/composer.lock @@ -1927,16 +1927,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.46", + "version": "3.0.47", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", "shasum": "" }, "require": { @@ -2017,7 +2017,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" }, "funding": [ { @@ -2033,7 +2033,7 @@ "type": "tidelift" } ], - "time": "2025-06-26T16:29:55+00:00" + "time": "2025-10-06T01:07:24+00:00" }, { "name": "psr/container", @@ -3635,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "2.2.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "19a3ab2ae99578861dd1a7b4fdbfb62b37d09447" + "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/19a3ab2ae99578861dd1a7b4fdbfb62b37d09447", - "reference": "19a3ab2ae99578861dd1a7b4fdbfb62b37d09447", + "url": "https://api.github.com/repos/utopia-php/database/zipball/a91e04080d7f13c35c4885dea0ffebc33cd33e1f", + "reference": "a91e04080d7f13c35c4885dea0ffebc33cd33e1f", "shasum": "" }, "require": { @@ -3685,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.2.0" + "source": "https://github.com/utopia-php/database/tree/2.3.1" }, - "time": "2025-09-26T02:23:30+00:00" + "time": "2025-10-06T04:29:14+00:00" }, { "name": "utopia-php/detector", diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 2d35ce92ae..522e160a87 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -45,32 +45,27 @@ class TransactionState ?string $transactionId = null, array $queries = [] ): Document { - // If no transaction, use normal database retrieval if ($transactionId === null) { return $this->dbForProject->getDocument($collectionId, $documentId, $queries); } $state = $this->getTransactionState($transactionId); - // Check if document exists in transaction state if (isset($state[$collectionId][$documentId])) { $docState = $state[$collectionId][$documentId]; if (!$docState['exists']) { - // Document was deleted in transaction return new Document(); } if ($docState['action'] === 'create') { - // Document was created in transaction, return the created version return $this->applyProjection($docState['document'], $queries); } if ($docState['action'] === 'update' || $docState['action'] === 'upsert') { - // This is an update to an existing document, merge with committed version + // Merge with committed version $committedDoc = $this->dbForProject->getDocument($collectionId, $documentId, $queries); if (!$committedDoc->isEmpty()) { - // Apply the updates from transaction foreach ($docState['document']->getAttributes() as $key => $value) { if ($key !== '$id') { $committedDoc->setAttribute($key, $value); @@ -79,13 +74,11 @@ class TransactionState // Reapply projection in case transaction added new fields return $this->applyProjection($committedDoc, $queries); } elseif ($docState['action'] === 'upsert') { - // Upsert created a new document since committed doc doesn't exist return $this->applyProjection($docState['document'], $queries); } } } - // Document not affected by transaction, return committed version return $this->dbForProject->getDocument($collectionId, $documentId, $queries); } @@ -307,18 +300,21 @@ class TransactionState /** * Apply bulk upsert to documents in transaction state * - * This allows bulk operations within a transaction to see each other's changes. + * This merges partial upsert data with full documents from transaction state, + * preventing validation errors when upserting documents created in the same transaction. * * @param string $collectionId Collection ID - * @param array $documents Array of Document objects to upsert + * @param array $documents Array of Document objects to upsert (can be partial) * @param array &$state Transaction state (passed by reference) - * @return void + * @return array Merged documents ready for database upsert */ public function applyBulkUpsertToState( string $collectionId, array $documents, array &$state - ): void { + ): array { + $mergedDocuments = []; + foreach ($documents as $doc) { if (!($doc instanceof Document)) { continue; @@ -329,7 +325,7 @@ class TransactionState continue; } - // If document exists in state, update it; otherwise it will be handled by DB upsert + // If document exists in state, update it and use the merged version if (isset($state[$collectionId][$docId])) { // Apply updates to existing state document foreach ($doc->getArrayCopy() as $key => $value) { @@ -337,8 +333,15 @@ class TransactionState $state[$collectionId][$docId]->setAttribute($key, $value); } } + // Use the full merged document from state + $mergedDocuments[] = $state[$collectionId][$docId]; + } else { + // Document not in state - use original partial data for DB upsert + $mergedDocuments[] = $doc; } } + + return $mergedDocuments; } /** diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 5f6fa4f1a6..c3ba45bdce 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -113,7 +113,6 @@ class Create extends Action throw new Exception(Exception::COLLECTION_NOT_FOUND); } - // Check if collection has relationships for bulk operations if (\in_array($operation['action'], ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) { $hasRelationships = \array_filter( $collection->getAttribute('attributes', []), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index d522c699a8..e33a731d0e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -103,7 +103,6 @@ class Update extends Action */ public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void { - if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); } @@ -128,8 +127,6 @@ class Update extends Action if ($commit) { $operations = []; - - // Track metrics for usage stats $totalOperations = 0; $databaseOperations = []; @@ -139,15 +136,12 @@ class Update extends Action 'status' => 'committing', ]))); - // Fetch operations ordered by sequence by default to replay operations in exact order they were created $operations = Authorization::skip(fn () => $dbForProject->find('transactionLogs', [ Query::equal('transactionInternalId', [$transaction->getSequence()]), Query::orderAsc(), Query::limit(PHP_INT_MAX), ])); - - // Track transaction state for cross-operation visibility $state = []; foreach ($operations as $operation) { @@ -159,7 +153,6 @@ class Update extends Action $action = $operation['action']; $data = $operation['data']; - // For delete operations, fetch the document before deleting for realtime events if ($action === 'delete' && $documentId && empty($data)) { $doc = $dbForProject->getDocument($collectionId, $documentId); if (!$doc->isEmpty()) { @@ -168,7 +161,6 @@ class Update extends Action } } - // Track operations for stats $totalOperations++; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + 1; @@ -176,7 +168,6 @@ class Update extends Action $data = $data->getArrayCopy(); } - // Execute the operation based on its type switch ($action) { case 'create': $this->handleCreateOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); @@ -217,44 +208,37 @@ class Update extends Action new Document(['status' => 'committed']) )); - // Clear the transaction logs $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transaction); }); } catch (NotFoundException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e); } catch (DuplicateException|ConflictException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e); } catch (StructureException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); } catch (LimitException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage()); } catch (TransactionException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage()); } catch (QueryException $e) { - // Transaction has been rolled back, now mark it as failed Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'failed', ]))); @@ -264,7 +248,6 @@ class Update extends Action $queueForStatsUsage ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $totalOperations); - // Add per-database metrics foreach ($databaseOperations as $sequence => $count) { $queueForStatsUsage->addMetric( str_replace('{databaseInternalId}', $sequence, METRIC_DATABASE_ID_OPERATIONS_WRITES), @@ -272,8 +255,6 @@ class Update extends Action ); } - // Trigger realtime events for each operation - foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; @@ -316,7 +297,6 @@ class Update extends Action $eventAction = 'create'; $docId = $documentId ?? $data['$id'] ?? null; if ($docId) { - // Fetch the created document from the database $doc = $dbForProject->getDocument($collectionId, $docId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; @@ -328,7 +308,6 @@ class Update extends Action case 'decrement': $eventAction = 'update'; if ($documentId) { - // Fetch the updated document from the database $doc = $dbForProject->getDocument($collectionId, $documentId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; @@ -338,15 +317,13 @@ class Update extends Action case 'delete': $eventAction = 'delete'; if ($documentId && !empty($data)) { - // For delete, use the fetched document data (fetched before deletion) $documentsToTrigger[] = new Document(array_merge($data, ['$id' => $documentId])); } break; case 'upsert': - $eventAction = 'update'; // Upsert is treated as update for events + $eventAction = 'update'; $docId = $documentId ?? $data['$id'] ?? null; if ($docId) { - // Fetch the upserted document from the database $doc = $dbForProject->getDocument($collectionId, $docId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; @@ -360,15 +337,11 @@ class Update extends Action break; } - // Trigger events for each document - $eventString = "databases.[databaseId].{$contextKey}s.[{$groupId}].{$resourcePlural}.[{$resourceId}]." . $eventAction; $queueForEvents->setEvent($eventString); foreach ($documentsToTrigger as $doc) { - - // Add table/collection IDs to the payload for realtime channels $payload = $doc->getArrayCopy(); $payload['$tableId'] = $collection->getId(); $payload['$collectionId'] = $collection->getId(); @@ -464,7 +437,6 @@ class Update extends Action $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { - // Update the state document directly without timestamp wrapper $state[$collectionId][$documentId] = $dbForProject->updateDocument( $collectionId, $documentId, @@ -473,7 +445,6 @@ class Update extends Action return; } - // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { $document = $dbForProject->updateDocument( $collectionId, @@ -510,15 +481,21 @@ class Update extends Action $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { - // Upsert the state document directly without timestamp wrapper + // Merge partial upsert data with full document from transaction state + $existingDoc = $state[$collectionId][$documentId]; + foreach ($data as $key => $value) { + if ($key !== '$id') { + $existingDoc->setAttribute($key, $value); + } + } + $state[$collectionId][$documentId] = $dbForProject->upsertDocument( $collectionId, - new Document($data), + $existingDoc, ); return; } - // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { $state[$collectionId][$documentId] = $dbForProject->upsertDocument( $collectionId, @@ -549,13 +526,11 @@ class Update extends Action $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { - // Delete without timestamp wrapper $dbForProject->deleteDocument($collectionId, $documentId); unset($state[$collectionId][$documentId]); return; } - // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, &$state) { $deleted = $dbForProject->deleteDocument($collectionId, $documentId); if (!$deleted) { @@ -591,7 +566,6 @@ class Update extends Action $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { - // Increment without timestamp wrapper $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, @@ -602,7 +576,6 @@ class Update extends Action return; } - // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data) { $dbForProject->increaseDocumentAttribute( collection: $collectionId, @@ -638,7 +611,6 @@ class Update extends Action $dependent = isset($state[$collectionId][$documentId]); if ($dependent) { - // Decrement without timestamp wrapper $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, @@ -649,8 +621,6 @@ class Update extends Action return; } - // Use timestamp wrapper for independent operations - // Use timestamp wrapper for independent operations $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, @@ -681,7 +651,6 @@ class Update extends Action array &$state ): void { $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { - // Convert data arrays to Document objects if needed $documents = \array_map(function ($doc) { return $doc instanceof Document ? $doc : new Document($doc); }, $data); @@ -721,29 +690,50 @@ class Update extends Action $queries = Query::parseQueries($data['queries'] ?? []); $updateData = new Document($data['data']); - // First, update documents in the committed database + $dependentDocs = []; + + $transactionState->applyBulkUpdateToState($collectionId, $updateData, $queries, $state); + + // Clone the document before passing to updateDocuments to prevent mutation + // The database layer mutates the input document, which would corrupt transaction state $dbForProject->updateDocuments( $collectionId, - $updateData, + clone $updateData, $queries, - onNext: function (Document $updated, Document $old) use (&$state, $collectionId, $createdAt) { - // Check if this document was created/modified in this transaction + onNext: function (Document $updated, Document $old) use (&$state, $collectionId, $createdAt, &$dependentDocs) { $dependent = isset($state[$collectionId][$updated->getId()]); - // If not in transaction state, check for timestamp conflicts - if (!$dependent) { + if ($dependent) { + $dependentDocs[] = $updated->getId(); + } else { $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); if ($oldUpdatedAt > $createdAt) { throw new ConflictException('Document was updated after the request timestamp'); } + $state[$collectionId][$updated->getId()] = $updated; } - - $state[$collectionId][$updated->getId()] = $updated; } ); - // Also update documents in the transaction state that match the query - $transactionState->applyBulkUpdateToState($collectionId, $updateData, $queries, $state); + // Re-write dependent documents from state to database to fix partial updates + if (!empty($dependentDocs)) { + $documentsToRewrite = []; + foreach ($dependentDocs as $docId) { + if (isset($state[$collectionId][$docId])) { + $documentsToRewrite[] = $state[$collectionId][$docId]; + } + } + + if (!empty($documentsToRewrite)) { + $dbForProject->upsertDocuments( + $collectionId, + $documentsToRewrite, + onNext: function (Document $upserted) use (&$state, $collectionId) { + $state[$collectionId][$upserted->getId()] = $upserted; + } + ); + } + } } /** @@ -767,25 +757,19 @@ class Update extends Action \DateTime $createdAt, array &$state ): void { - // Convert data arrays to Document objects if needed $documents = \array_map(function ($doc) { return $doc instanceof Document ? $doc : new Document($doc); }, $data); - // First, apply upserts to documents in the transaction state - // This ensures documents created in this transaction are updated properly - $transactionState->applyBulkUpsertToState($collectionId, $documents, $state); + $mergedDocuments = $transactionState->applyBulkUpsertToState($collectionId, $documents, $state); - // Then run bulk upsert on committed database, checking manually in callback $dbForProject->upsertDocuments( $collectionId, - $documents, + $mergedDocuments, onNext: function (Document $upserted, ?Document $old) use (&$state, $collectionId, $createdAt) { if ($old !== null) { - // This is an update - check if document was created/modified in this transaction $dependent = isset($state[$collectionId][$upserted->getId()]); - // If not in transaction state, check for timestamp conflicts if (!$dependent) { $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); if ($oldUpdatedAt > $createdAt) { @@ -794,7 +778,6 @@ class Update extends Action } } - // If $old is null, this is a create operation - no timestamp check needed $state[$collectionId][$upserted->getId()] = $upserted; } ); @@ -830,7 +813,6 @@ class Update extends Action onNext: function (Document $deleted, Document $old) use (&$state, $collectionId, $createdAt) { $dependent = isset($state[$collectionId][$deleted->getId()]); - // If not in transaction state, check for timestamp conflicts if (!$dependent) { $oldUpdatedAt = new \DateTime($old->getUpdatedAt()); if ($oldUpdatedAt > $createdAt) { @@ -838,14 +820,12 @@ class Update extends Action } } - // Remove from state after successful deletion if (isset($state[$collectionId][$deleted->getId()])) { unset($state[$collectionId][$deleted->getId()]); } } ); - // Also delete documents in the transaction state that match the query $transactionState->applyBulkDeleteToState($collectionId, $queries, $state); } } From eb7306a4fa7f4105d8afbe9dc39e6eae077e699c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 7 Oct 2025 22:17:59 +1300 Subject: [PATCH 294/385] Add missing state handlers --- src/Appwrite/Databases/TransactionState.php | 105 +++++++++++++++ .../Transactions/TransactionsBase.php | 123 ++++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 8df2bc657c..23dc6fc2e9 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -423,6 +423,33 @@ class TransactionState ]; break; + case 'increment': + case 'decrement': + $attribute = $data['attribute'] ?? null; + $value = $data['value'] ?? 1; + + if ($attribute) { + if (isset($state[$collectionId][$documentId])) { + $existingDocument = $state[$collectionId][$documentId]['document']; + $currentValue = $existingDocument->getAttribute($attribute, 0); + $newValue = $action === 'increment' ? $currentValue + $value : $currentValue - $value; + $existingDocument->setAttribute($attribute, $newValue); + + $currentAction = $state[$collectionId][$documentId]['action']; + if ($currentAction !== 'create' && $currentAction !== 'upsert') { + $state[$collectionId][$documentId]['action'] = 'update'; + } + } else { + $newValue = $action === 'increment' ? $value : -$value; + $state[$collectionId][$documentId] = [ + 'action' => 'update', + 'document' => new Document([$attribute => $newValue]), + 'exists' => true + ]; + } + } + break; + case 'bulkCreate': if (\is_array($data)) { foreach ($data as $doc) { @@ -437,6 +464,84 @@ class TransactionState } } break; + + case 'bulkUpdate': + if (isset($data['queries']) && isset($data['data'])) { + $queries = Query::parseQueries($data['queries'] ?? []); + $updateData = $data['data']; + + foreach ($state[$collectionId] ?? [] as $docId => $entry) { + if (!$entry['exists']) { + continue; + } + + $document = $entry['document']; + $filters = $this->extractFilters($queries); + + if ($this->documentMatchesFilters($document, $filters)) { + foreach ($updateData as $key => $value) { + if ($key !== '$id') { + $document->setAttribute($key, $value); + } + } + + $currentAction = $state[$collectionId][$docId]['action']; + if ($currentAction !== 'create' && $currentAction !== 'upsert') { + $state[$collectionId][$docId]['action'] = 'update'; + } + } + } + } + break; + + case 'bulkUpsert': + if (\is_array($data)) { + foreach ($data as $doc) { + if ($doc instanceof Document) { + $doc = $doc->getArrayCopy(); + } + + $docId = $doc['$id'] ?? null; + if (!$docId) { + continue; + } + + if (isset($state[$collectionId][$docId])) { + $existingDocument = $state[$collectionId][$docId]['document']; + foreach ($doc as $key => $value) { + $existingDocument->setAttribute($key, $value); + } + } else { + $state[$collectionId][$docId] = [ + 'action' => 'upsert', + 'document' => new Document($doc), + 'exists' => true + ]; + } + } + } + break; + + case 'bulkDelete': + if (isset($data['queries'])) { + $queries = Query::parseQueries($data['queries'] ?? []); + $filters = $this->extractFilters($queries); + + foreach ($state[$collectionId] ?? [] as $docId => $entry) { + if (!$entry['exists']) { + continue; + } + + $document = $entry['document']; + if ($this->documentMatchesFilters($document, $filters)) { + $state[$collectionId][$docId] = [ + 'action' => 'delete', + 'exists' => false + ]; + } + } + } + break; } } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index 1ea8d0cc9a..e50bb9f2bf 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -4613,4 +4613,127 @@ trait TransactionsBase $this->assertEquals(404, $response['headers']['status-code']); } + + /** + * Test that bulkUpdate can match documents created in the same transaction + * This tests the fix for the bug where applyBulkUpdateToState was treating + * state entries as Documents instead of arrays with 'document' keys + */ + public function testBulkUpdateMatchesCreatedDocsInSameTransaction(): void + { + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateStateDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'TestTable', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'flag', + 'size' => 256, + 'required' => false, + ]); + + sleep(3); + + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Create 3 documents with status='pending' in transaction + $docIds = []; + for ($i = 1; $i <= 3; $i++) { + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => 'test_' . $i, + 'data' => [ + 'status' => 'pending' + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $docIds[] = $response['body']['$id']; + } + + // Bulk update all documents with status='pending' to add flag='processed' + // This should match all 3 documents created above in the same transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'flag' => 'processed' + ], + 'queries' => [Query::equal('status', ['pending'])->toString()], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify all 3 documents have the flag set + foreach ($docIds as $docId) { + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/{$docId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('pending', $response['body']['status']); + $this->assertEquals('processed', $response['body']['flag'], 'Bulk update should have matched document created in same transaction'); + } + } } From 066e0bc236a8bc2a38345effe3d907621cc34ee4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 7 Oct 2025 23:33:19 +1300 Subject: [PATCH 295/385] Validate inc/dec operation value is numeric --- src/Appwrite/Utopia/Database/Validator/Operation.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 4e421689e3..e6884ac677 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -202,6 +202,11 @@ class Operation extends Validator $this->description = "Key '{$attributeKey}' is required in data for {$action}"; return false; } + // Validate 'value' is numeric if provided (defaults to 1 if omitted) + if (\array_key_exists('value', $value['data']) && !\is_numeric($value['data']['value'])) { + $this->description = "Key 'value' must be a numeric value for {$action}"; + return false; + } } return true; From 6e0d3767465716cbe0bb82110685a89622ac9980 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 7 Oct 2025 16:07:47 +0530 Subject: [PATCH 296/385] use filevalidator --- app/controllers/api/account.php | 16 +- composer.lock | 269 ++++++++++++++++---------------- 2 files changed, 144 insertions(+), 141 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index af9a772b17..6836c6bfaf 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -58,6 +58,7 @@ use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; use Utopia\Locale\Locale; +use Utopia\Storage\Validator\FileName; use Utopia\System\System; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; @@ -69,12 +70,6 @@ use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/console/auth/oauth2/success'; $oauthDefaultFailure = '/console/auth/oauth2/failure'; -function containsDirectoryTraversal(string $path) -{ - // Matches '../', './', '/..', or absolute paths starting with '/' - return preg_match('/(\.\.\/|\.\/|\/\.\.|\/)/', $path); -} - function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails) { $subject = $locale->getText("emails.sessionAlert.subject"); @@ -2307,7 +2302,8 @@ App::post('/v1/account/tokens/email') $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); - if (containsDirectoryTraversal($smtpBaseTemplate)) { + $validator = new FileName(); + if (!$validator->isValid($smtpBaseTemplate)) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); } @@ -3621,7 +3617,8 @@ App::post('/v1/account/verification') $customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); - if (containsDirectoryTraversal($smtpBaseTemplate)) { + $validator = new FileName(); + if (!$validator->isValid($smtpBaseTemplate)) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); } @@ -4722,7 +4719,8 @@ App::post('/v1/account/mfa/challenge') $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); - if (containsDirectoryTraversal($smtpBaseTemplate)) { + $validator = new FileName(); + if (!$validator->isValid($smtpBaseTemplate)) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); } diff --git a/composer.lock b/composer.lock index bd70f229ad..8e600c326f 100644 --- a/composer.lock +++ b/composer.lock @@ -756,24 +756,21 @@ }, { "name": "google/protobuf", - "version": "v4.32.0", + "version": "v4.32.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", "shasum": "" }, "require": { "php": ">=8.1.0" }, - "provide": { - "ext-protobuf": "*" - }, "require-dev": { "phpunit/phpunit": ">=5.0.0 <8.5.27" }, @@ -797,9 +794,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1" }, - "time": "2025-08-14T20:00:33+00:00" + "time": "2025-09-14T05:14:52+00:00" }, { "name": "league/csv", @@ -1162,20 +1159,20 @@ }, { "name": "open-telemetry/api", - "version": "1.5.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5" + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", "shasum": "" }, "require": { - "open-telemetry/context": "^1.0", + "open-telemetry/context": "^1.4", "php": "^8.1", "psr/log": "^1.1|^2.0|^3.0", "symfony/polyfill-php82": "^1.26" @@ -1191,7 +1188,7 @@ ] }, "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.7.x-dev" } }, "autoload": { @@ -1228,20 +1225,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-07T23:07:38+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/context", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", "shasum": "" }, "require": { @@ -1287,7 +1284,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-13T01:12:00+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -1355,16 +1352,16 @@ }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.5.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" + "reference": "673af5b06545b513466081884b47ef15a536edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", + "reference": "673af5b06545b513466081884b47ef15a536edde", "shasum": "" }, "require": { @@ -1414,27 +1411,27 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-15T23:07:07+00:00" + "time": "2025-09-17T23:10:12+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.7.1", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06" + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/52690d4b37ae4f091af773eef3c238ed2bc0aa06", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.4", - "open-telemetry/context": "^1.0", + "open-telemetry/api": "^1.7", + "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", "php-http/discovery": "^1.14", @@ -1468,7 +1465,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -1511,7 +1508,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-05T07:17:06+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1572,16 +1569,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226", + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226", "shasum": "" }, "require": { @@ -1635,7 +1632,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2025-09-24T15:12:37+00:00" }, { "name": "paragonie/random_compat", @@ -1930,16 +1927,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.46", + "version": "3.0.47", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", "shasum": "" }, "require": { @@ -2020,7 +2017,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" }, "funding": [ { @@ -2036,7 +2033,7 @@ "type": "tidelift" } ], - "time": "2025-06-26T16:29:55+00:00" + "time": "2025-10-06T01:07:24+00:00" }, { "name": "psr/container", @@ -2599,16 +2596,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { @@ -2675,7 +2672,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.3" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -2695,7 +2692,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T07:45:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", @@ -3638,16 +3635,16 @@ }, { "name": "utopia-php/database", - "version": "1.4.1", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b5ea4d133a1a4e747b7522e61e072289129a06f4" + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b5ea4d133a1a4e747b7522e61e072289129a06f4", - "reference": "b5ea4d133a1a4e747b7522e61e072289129a06f4", + "url": "https://api.github.com/repos/utopia-php/database/zipball/56efe4daaf23abb753553acffccdcc04cd6178c9", + "reference": "56efe4daaf23abb753553acffccdcc04cd6178c9", "shasum": "" }, "require": { @@ -3688,9 +3685,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.4.1" + "source": "https://github.com/utopia-php/database/tree/1.5.1" }, - "time": "2025-09-05T13:23:52+00:00" + "time": "2025-10-01T04:44:14+00:00" }, { "name": "utopia-php/detector", @@ -3795,16 +3792,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7" + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/650463d2a1525273eb03223c48da9fb1a768bbf7", - "reference": "650463d2a1525273eb03223c48da9fb1a768bbf7", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/d5f903e93c105407da6374e411c4805b7decd8a8", + "reference": "d5f903e93c105407da6374e411c4805b7decd8a8", "shasum": "" }, "require": { @@ -3850,9 +3847,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.0" + "source": "https://github.com/utopia-php/domains/tree/0.8.1" }, - "time": "2025-05-16T10:03:59+00:00" + "time": "2025-10-03T11:58:53+00:00" }, { "name": "utopia-php/dsn", @@ -3942,16 +3939,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.27", + "version": "0.33.28", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37" + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/d9d10a895e85c8c7675220347cc6109db9d3bd37", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", "shasum": "" }, "require": { @@ -3983,9 +3980,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.27" + "source": "https://github.com/utopia-php/http/tree/0.33.28" }, - "time": "2025-09-07T18:40:53+00:00" + "time": "2025-09-25T10:44:24+00:00" }, { "name": "utopia-php/image", @@ -4190,16 +4187,16 @@ }, { "name": "utopia-php/migration", - "version": "1.0.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "38171023efd3abe650d2abc5ac65f5df52311da6" + "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6", - "reference": "38171023efd3abe650d2abc5ac65f5df52311da6", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/42ff497c5231f5a727d1e229419ff1d2195d8093", + "reference": "42ff497c5231f5a727d1e229419ff1d2195d8093", "shasum": "" }, "require": { @@ -4240,9 +4237,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.0.1" + "source": "https://github.com/utopia-php/migration/tree/1.2.0" }, - "time": "2025-08-28T13:41:25+00:00" + "time": "2025-09-24T10:32:24+00:00" }, { "name": "utopia-php/orchestration", @@ -4569,16 +4566,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.13", + "version": "0.18.14", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "3d8ce53ae042173bf230445e996056c5f65ded22" + "reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/3d8ce53ae042173bf230445e996056c5f65ded22", - "reference": "3d8ce53ae042173bf230445e996056c5f65ded22", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/4f14ec952c6f4006dd0613e55bbf7631814fbc00", + "reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00", "shasum": "" }, "require": { @@ -4621,9 +4618,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.13" + "source": "https://github.com/utopia-php/storage/tree/0.18.14" }, - "time": "2025-05-26T13:10:35+00:00" + "time": "2025-10-07T10:21:47+00:00" }, { "name": "utopia-php/swoole", @@ -5007,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.3.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a" + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/375a6c9b168db6fdf58fbe0d49d2261d80700b4a", - "reference": "375a6c9b168db6fdf58fbe0d49d2261d80700b4a", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", + "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", "shasum": "" }, "require": { @@ -5052,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.3.2" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.3" }, - "time": "2025-09-05T15:50:35+00:00" + "time": "2025-10-01T06:25:19+00:00" }, { "name": "doctrine/annotations", @@ -5281,16 +5278,16 @@ }, { "name": "laravel/pint", - "version": "v1.24.0", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", "shasum": "" }, "require": { @@ -5301,9 +5298,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.82.2", - "illuminate/view": "^11.45.1", - "larastan/larastan": "^3.5.0", + "friendsofphp/php-cs-fixer": "^3.87.2", + "illuminate/view": "^11.46.0", + "larastan/larastan": "^3.7.1", "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", @@ -5314,9 +5311,6 @@ ], "type": "project", "autoload": { - "files": [ - "overrides/Runner/Parallel/ProcessFactory.php" - ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -5346,7 +5340,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-10T18:09:32+00:00" + "time": "2025-09-19T02:57:12+00:00" }, { "name": "matthiasmullie/minify", @@ -6236,16 +6230,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.25", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "049c011e01be805202d8eebedef49f769a8ec7b7" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/049c011e01be805202d8eebedef49f769a8ec7b7", - "reference": "049c011e01be805202d8eebedef49f769a8ec7b7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -6270,7 +6264,7 @@ "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", + "sebastian/exporter": "^4.0.8", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -6319,7 +6313,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -6343,7 +6337,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:38:31+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "psr/cache", @@ -6835,16 +6829,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -6900,15 +6894,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", @@ -7491,16 +7497,16 @@ }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -7565,7 +7571,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -7585,7 +7591,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/filesystem", @@ -8128,16 +8134,16 @@ }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -8169,7 +8175,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -8189,20 +8195,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:42:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -8217,7 +8223,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -8260,7 +8265,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -8280,7 +8285,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "textalk/websocket", @@ -8512,7 +8517,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8536,5 +8541,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From e935fc251026d21a4433672ab26072a1f799b671 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 8 Oct 2025 09:52:28 +0530 Subject: [PATCH 297/385] Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds minor version releases for all client and server SDKs to deprecate the `createVerification` method in the Account service and introduce the new `createEmailVerification` method as its replacement. Updated SDKs: Client SDKs: - Android: 11.0.0 → 11.1.0 - Web: 21.0.0 → 21.1.0 - Flutter: 20.0.0 → 20.1.0 - Apple: 13.0.0 → 13.1.0 - React Native: 0.15.0 → 0.16.0 Server SDKs: - Node.js: 20.0.0 → 20.1.0 - PHP: 17.2.0 → 17.3.0 - Python: 13.2.0 → 13.3.0 - Ruby: 19.0.0 → 19.1.0 - Go: 0.13.0 → 0.14.0 - .NET: 0.19.0 → 0.20.0 - Dart: 19.0.0 → 19.1.0 - Kotlin: 12.0.0 → 12.1.0 - Swift: 13.0.0 → 13.1.0 Console SDK: - CLI: 10.0.1 → 10.1.0 --- app/config/platforms.php | 30 +++++++++---------- .../account/create-email-verification.md | 2 +- .../account/create-phone-verification.md | 2 +- .../examples/account/create-verification.md | 2 +- .../account/update-email-verification.md | 2 +- .../account/update-phone-verification.md | 2 +- .../examples/account/update-verification.md | 2 +- .../account/create-email-verification.md | 2 +- .../account/create-phone-verification.md | 2 +- .../examples/account/create-verification.md | 2 +- .../account/update-email-verification.md | 2 +- .../account/update-phone-verification.md | 2 +- .../examples/account/update-verification.md | 2 +- docs/sdks/android/CHANGELOG.md | 5 ++++ docs/sdks/apple/CHANGELOG.md | 5 ++++ docs/sdks/cli/CHANGELOG.md | 5 ++++ docs/sdks/dart/CHANGELOG.md | 5 ++++ docs/sdks/dotnet/CHANGELOG.md | 5 ++++ docs/sdks/flutter/CHANGELOG.md | 5 ++++ docs/sdks/go/CHANGELOG.md | 5 ++++ docs/sdks/kotlin/CHANGELOG.md | 5 ++++ docs/sdks/nodejs/CHANGELOG.md | 5 ++++ docs/sdks/php/CHANGELOG.md | 5 ++++ docs/sdks/python/CHANGELOG.md | 5 ++++ docs/sdks/react-native/CHANGELOG.md | 5 ++++ docs/sdks/ruby/CHANGELOG.md | 5 ++++ docs/sdks/swift/CHANGELOG.md | 5 ++++ docs/sdks/web/CHANGELOG.md | 5 ++++ src/Appwrite/Platform/Tasks/SDKs.php | 28 ++++++++++++++++- 29 files changed, 129 insertions(+), 28 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index e5a297fb0c..663c254b66 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '21.0.0', + 'version' => '21.1.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '20.0.0', + 'version' => '20.1.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '13.0.0', + 'version' => '13.1.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '11.0.0', + 'version' => '11.1.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -139,7 +139,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.15.0', + 'version' => '0.16.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -226,7 +226,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '10.0.1', + 'version' => '10.1.0', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '20.0.0', + 'version' => '20.1.0', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -281,7 +281,7 @@ return [ [ 'key' => 'php', 'name' => 'PHP', - 'version' => '17.2.0', + 'version' => '17.3.0', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -300,7 +300,7 @@ return [ [ 'key' => 'python', 'name' => 'Python', - 'version' => '13.2.0', + 'version' => '13.3.0', 'url' => 'https://github.com/appwrite/sdk-for-python', 'package' => 'https://pypi.org/project/appwrite/', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '19.0.0', + 'version' => '19.1.0', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => '0.13.0', + 'version' => '0.14.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.19.0', + 'version' => '0.20.0', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '19.0.0', + 'version' => '19.1.0', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '12.0.0', + 'version' => '12.1.0', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '13.0.0', + 'version' => '13.1.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md b/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md index ed5479dbe5..63fcd5765e 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/create-email-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification HTTP/1.1 +POST /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/create-phone-verification.md b/docs/examples/1.8.x/client-rest/examples/account/create-phone-verification.md index 57b3b7d160..b48c9249b3 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/create-phone-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/create-phone-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification/phone HTTP/1.1 +POST /v1/account/verifications/phone HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/create-verification.md b/docs/examples/1.8.x/client-rest/examples/account/create-verification.md index ed5479dbe5..63fcd5765e 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/create-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/create-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification HTTP/1.1 +POST /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md b/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md index a4dcbf76a3..c7c6c34e52 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/update-email-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification HTTP/1.1 +PUT /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/update-phone-verification.md b/docs/examples/1.8.x/client-rest/examples/account/update-phone-verification.md index 1d4dc22520..faa6478150 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/update-phone-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/update-phone-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification/phone HTTP/1.1 +PUT /v1/account/verifications/phone HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/update-verification.md b/docs/examples/1.8.x/client-rest/examples/account/update-verification.md index a4dcbf76a3..c7c6c34e52 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/update-verification.md +++ b/docs/examples/1.8.x/client-rest/examples/account/update-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification HTTP/1.1 +PUT /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md b/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md index ed5479dbe5..63fcd5765e 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/create-email-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification HTTP/1.1 +POST /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/create-phone-verification.md b/docs/examples/1.8.x/server-rest/examples/account/create-phone-verification.md index 57b3b7d160..b48c9249b3 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/create-phone-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/create-phone-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification/phone HTTP/1.1 +POST /v1/account/verifications/phone HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/create-verification.md b/docs/examples/1.8.x/server-rest/examples/account/create-verification.md index ed5479dbe5..63fcd5765e 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/create-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/create-verification.md @@ -1,4 +1,4 @@ -POST /v1/account/verification HTTP/1.1 +POST /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md b/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md index a4dcbf76a3..c7c6c34e52 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/update-email-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification HTTP/1.1 +PUT /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/update-phone-verification.md b/docs/examples/1.8.x/server-rest/examples/account/update-phone-verification.md index 1d4dc22520..faa6478150 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/update-phone-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/update-phone-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification/phone HTTP/1.1 +PUT /v1/account/verifications/phone HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/update-verification.md b/docs/examples/1.8.x/server-rest/examples/account/update-verification.md index a4dcbf76a3..c7c6c34e52 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/update-verification.md +++ b/docs/examples/1.8.x/server-rest/examples/account/update-verification.md @@ -1,4 +1,4 @@ -PUT /v1/account/verification HTTP/1.1 +PUT /v1/account/verifications/email HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/sdks/android/CHANGELOG.md b/docs/sdks/android/CHANGELOG.md index 38d0c4be2d..b6b9e4072b 100644 --- a/docs/sdks/android/CHANGELOG.md +++ b/docs/sdks/android/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 11.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 8.2.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/apple/CHANGELOG.md b/docs/sdks/apple/CHANGELOG.md index f62eb708f1..dce2df1804 100644 --- a/docs/sdks/apple/CHANGELOG.md +++ b/docs/sdks/apple/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 13.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 10.2.0 * Update sdk to use swift-native doc comments instead of jsdoc styled comments as per [Swift Documentation Comments](https://github.com/swiftlang/swift/blob/main/docs/DocumentationComments.md) diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index 5af193d491..289c1ef0cb 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 10.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 10.0.1 * Fix CLI Dart model generation issues diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index 7e33794153..c25e66708b 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 19.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 18.1.0 * Add `orderRandom` query support diff --git a/docs/sdks/dotnet/CHANGELOG.md b/docs/sdks/dotnet/CHANGELOG.md index bbfee108a7..7bd7d1d267 100644 --- a/docs/sdks/dotnet/CHANGELOG.md +++ b/docs/sdks/dotnet/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 0.20.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 0.15.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index f704415675..176efe4a5d 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 20.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 19.1.0 * Add `orderRandom` query support diff --git a/docs/sdks/go/CHANGELOG.md b/docs/sdks/go/CHANGELOG.md index 418c7db5e5..7c72fd03be 100644 --- a/docs/sdks/go/CHANGELOG.md +++ b/docs/sdks/go/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 0.14.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 0.9.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/kotlin/CHANGELOG.md b/docs/sdks/kotlin/CHANGELOG.md index c7194d5391..40c32751d7 100644 --- a/docs/sdks/kotlin/CHANGELOG.md +++ b/docs/sdks/kotlin/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 12.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 9.1.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/nodejs/CHANGELOG.md b/docs/sdks/nodejs/CHANGELOG.md index 7d6926dd1d..7225d3d92f 100644 --- a/docs/sdks/nodejs/CHANGELOG.md +++ b/docs/sdks/nodejs/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 20.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 17.2.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/php/CHANGELOG.md b/docs/sdks/php/CHANGELOG.md index d5a323c4cb..b1221264b2 100644 --- a/docs/sdks/php/CHANGELOG.md +++ b/docs/sdks/php/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 17.3.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 15.1.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/python/CHANGELOG.md b/docs/sdks/python/CHANGELOG.md index ff63134a20..584aa5cf5c 100644 --- a/docs/sdks/python/CHANGELOG.md +++ b/docs/sdks/python/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 13.3.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 11.1.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/react-native/CHANGELOG.md b/docs/sdks/react-native/CHANGELOG.md index eda0bca26e..79ade8cc6c 100644 --- a/docs/sdks/react-native/CHANGELOG.md +++ b/docs/sdks/react-native/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +## 0.16.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 0.11.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/ruby/CHANGELOG.md b/docs/sdks/ruby/CHANGELOG.md index 1f2e10beea..0498d7cfb5 100644 --- a/docs/sdks/ruby/CHANGELOG.md +++ b/docs/sdks/ruby/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 19.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 16.1.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/docs/sdks/swift/CHANGELOG.md b/docs/sdks/swift/CHANGELOG.md index 6cb1cca595..3d73a868f3 100644 --- a/docs/sdks/swift/CHANGELOG.md +++ b/docs/sdks/swift/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 13.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 10.2.0 * Update sdk to use swift-native doc comments instead of jsdoc styled comments as per [Swift Documentation Comments](https://github.com/swiftlang/swift/blob/main/docs/DocumentationComments.md) diff --git a/docs/sdks/web/CHANGELOG.md b/docs/sdks/web/CHANGELOG.md index 328ef1d5a8..a28d868075 100644 --- a/docs/sdks/web/CHANGELOG.md +++ b/docs/sdks/web/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 21.1.0 + +* Deprecate `createVerification` method in `Account` service +* Add `createEmailVerification` method in `Account` service + ## 18.2.0 * Add `incrementDocumentAttribute` and `decrementDocumentAttribute` support to `Databases` service diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 63ffddf950..47c72c4424 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -58,6 +58,8 @@ class SDKs extends Action $git ??= Console::confirm('Should we use git push? (yes/no)'); $git = $git === 'yes'; + $prUrls = []; + if ($git) { $production ??= Console::confirm('Type "Appwrite" to push code to production git repos'); $production = $production === 'Appwrite'; @@ -346,7 +348,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND if ($prReturnCode === 0) { Console::success("Successfully created pull request for {$language['name']} SDK"); if (!empty($prOutput)) { - Console::info("PR URL: " . end($prOutput)); + $prUrls[$language['name']] = end($prOutput); } } else { $errorMessage = implode("\n", $prOutput); @@ -366,6 +368,21 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND if ($updateReturnCode === 0) { Console::success("Successfully updated pull request for {$language['name']} SDK"); + + $prUrlCommand = 'cd ' . $target . ' && \ + gh pr view "' . $gitBranch . '" \ + --repo "' . $repoName . '" \ + --json url \ + --jq .url \ + 2>&1'; + + $prUrlOutput = []; + $prUrlReturnCode = 0; + \exec($prUrlCommand, $prUrlOutput, $prUrlReturnCode); + + if ($prUrlReturnCode === 0 && !empty($prUrlOutput)) { + $prUrls[$language['name']] = $prUrlOutput[0]; + } } else { $updateErrorMessage = implode("\n", $updateOutput); Console::error("Failed to update pull request for {$language['name']} SDK: " . $updateErrorMessage); @@ -396,5 +413,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } } } + + if (!empty($prUrls)) { + Console::log(''); + Console::log('Pull Request Summary'); + foreach ($prUrls as $sdkName => $url) { + Console::log("{$sdkName}: {$url}"); + } + Console::log(''); + } } } From adf9ec3e75608d88008e0b5eff6584714ad8f22a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 8 Oct 2025 13:05:46 +0530 Subject: [PATCH 298/385] add automatic releases --- src/Appwrite/Platform/Tasks/SDKs.php | 162 +++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 10 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 47c72c4424..87c70d214b 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -46,27 +46,35 @@ class SDKs extends Action ->param('git', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we use git push?', optional: true) ->param('production', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we push to production?', optional: true) ->param('message', null, new Nullable(new Text(256)), 'Commit Message', optional: true) + ->param('release', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we create releases?', optional: true) + ->param('commit', null, new Nullable(new WhiteList(['yes', 'no'])), 'Actually create releases (yes) or dry-run (no)?', optional: true) ->callback($this->action(...)); } - public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message): void + public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message, ?string $release, ?string $commit): void { $selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):'); $selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):')); $version ??= Console::confirm('Choose an Appwrite version'); - $git ??= Console::confirm('Should we use git push? (yes/no)'); - $git = $git === 'yes'; + $createRelease = ($release === 'yes'); + $commitRelease = ($commit === 'yes'); - $prUrls = []; + if (!$createRelease) { + $git ??= Console::confirm('Should we use git push? (yes/no)'); + $git = ($git === 'yes'); - if ($git) { - $production ??= Console::confirm('Type "Appwrite" to push code to production git repos'); - $production = $production === 'Appwrite'; - $message ??= Console::confirm('Please enter your commit message:'); + $prUrls = []; + $createPr = false; - $createPr = Console::confirm('Should we create pull request automatically? (yes/no)'); - $createPr = $createPr === 'yes'; + if ($git) { + $production ??= Console::confirm('Type "Appwrite" to push code to production git repos'); + $production = $production === 'Appwrite'; + $message ??= Console::confirm('Please enter your commit message:'); + + $createPr = Console::confirm('Should we create pull request automatically? (yes/no)'); + $createPr = ($createPr === 'yes'); + } } if (!\in_array($version, [ @@ -245,6 +253,97 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND throw new \Exception('Language "' . $language['key'] . '" not supported'); } + if ($createRelease) { + $releaseVersion = $language['version']; + + $repoName = $language['gitUserName'] . '/' . $language['gitRepoName']; + $releaseNotes = $this->extractReleaseNotes($changelog, $releaseVersion); + if (empty($releaseNotes)) { + $releaseNotes = "Release version {$releaseVersion}"; + } + + $releaseTitle = $releaseVersion; + $releaseTarget = $language['repoBranch'] ?? 'main'; + + if ($repoName === '/') { + Console::warning("{$language['name']} SDK is not an SDK, skipping release"); + continue; + } + + // Check if release already exists + $checkReleaseCommand = 'gh release view "' . $releaseVersion . '" --repo "' . $repoName . '" --json url --jq ".url" 2>/dev/null'; + $existingReleaseUrl = trim(\shell_exec($checkReleaseCommand) ?? ''); + + if (!empty($existingReleaseUrl)) { + Console::warning("Release {$releaseVersion} already exists for {$language['name']} SDK, skipping..."); + Console::info("Existing release: {$existingReleaseUrl}"); + continue; + } + + $previousVersion = ''; + $tagListCommand = 'gh release list --repo "' . $repoName . '" --limit 1 --json tagName --jq ".[0].tagName" 2>&1'; + $previousVersion = trim(\shell_exec($tagListCommand) ?? ''); + + $formattedNotes = "## What's Changed\n\n"; + $formattedNotes .= $releaseNotes . "\n\n"; + + if (!empty($previousVersion)) { + $formattedNotes .= "**Full Changelog**: https://github.com/" . $repoName . "/compare/" . $previousVersion . "..." . $releaseVersion; + } else { + $formattedNotes .= "**Full Changelog**: https://github.com/" . $repoName . "/releases/tag/" . $releaseVersion; + } + + if (!$commitRelease) { + Console::info("[DRY RUN] Would create release for {$language['name']} SDK:"); + Console::log(" Repository: {$repoName}"); + Console::log(" Version: {$releaseVersion}"); + Console::log(" Title: {$releaseTitle}"); + Console::log(" Target Branch: {$releaseTarget}"); + Console::log(" Previous Version: " . ($previousVersion ?: 'N/A')); + Console::log(" Release Notes:"); + Console::log(" " . str_replace("\n", "\n ", $formattedNotes)); + Console::log(''); + } else { + Console::info("Creating release {$releaseVersion} for {$language['name']} SDK..."); + + $tempNotesFile = \tempnam(\sys_get_temp_dir(), 'release_notes_'); + \file_put_contents($tempNotesFile, $formattedNotes); + + $releaseCommand = 'gh release create "' . $releaseVersion . '" \ + --repo "' . $repoName . '" \ + --title "' . $releaseTitle . '" \ + --notes-file "' . $tempNotesFile . '" \ + --target "' . $releaseTarget . '" \ + 2>&1'; + + $releaseOutput = []; + $releaseReturnCode = 0; + \exec($releaseCommand, $releaseOutput, $releaseReturnCode); + + \unlink($tempNotesFile); + + if ($releaseReturnCode === 0) { + // Extract release URL from output + $releaseUrl = ''; + foreach ($releaseOutput as $line) { + if (strpos($line, 'https://github.com/') !== false) { + $releaseUrl = trim($line); + break; + } + } + + Console::success("Successfully created release {$releaseVersion} for {$language['name']} SDK"); + if (!empty($releaseUrl)) { + Console::info("Release URL: {$releaseUrl}"); + } + } else { + $errorMessage = implode("\n", $releaseOutput); + Console::error("Failed to create release for {$language['name']} SDK: " . $errorMessage); + } + } + continue; + } + Console::info("Generating {$language['name']} SDK..."); $sdk = new SDK($config, new Swagger2($spec)); @@ -423,4 +522,47 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND Console::log(''); } } + + /** + * Extract release notes from changelog for a specific version + * + * @param string $changelog + * @param string $version + * @return string + */ + private function extractReleaseNotes(string $changelog, string $version): string + { + if (empty($changelog)) { + return ''; + } + + // Changelog version header pattern: ## 0.14.0 + $pattern = '/^##\s+' . preg_quote($version, '/') . '\s*$/m'; + $startPos = false; + if (preg_match($pattern, $changelog, $matches, PREG_OFFSET_CAPTURE)) { + $startPos = $matches[0][1]; + } + + if ($startPos === false) { + return ''; + } + + $contentStart = strpos($changelog, "\n", $startPos); + if ($contentStart === false) { + return ''; + } + $contentStart++; + + $nextHeaderPattern = '/^##?\s+/m'; + $remainingContent = substr($changelog, $contentStart); + + if (preg_match($nextHeaderPattern, $remainingContent, $matches, PREG_OFFSET_CAPTURE)) { + $endPos = $matches[0][1]; + $notes = substr($remainingContent, 0, $endPos); + } else { + $notes = $remainingContent; + } + + return trim($notes); + } } From 8d2df2b744c75533050411f65fb8e27c70d15682 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 8 Oct 2025 13:25:28 +0530 Subject: [PATCH 299/385] add go changes --- app/config/platforms.php | 2 +- docs/sdks/go/CHANGELOG.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 663c254b66..51930d1728 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => '0.14.0', + 'version' => 'v0.12.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, diff --git a/docs/sdks/go/CHANGELOG.md b/docs/sdks/go/CHANGELOG.md index 7c72fd03be..9191f77aa3 100644 --- a/docs/sdks/go/CHANGELOG.md +++ b/docs/sdks/go/CHANGELOG.md @@ -1,9 +1,10 @@ # Change Log -## 0.14.0 +## v0.12.0 * Deprecate `createVerification` method in `Account` service * Add `createEmailVerification` method in `Account` service +* Add `orderRandom` query support ## 0.9.0 From dcad82c46b9e70ecf007188bda6c84522ca54fff Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 13:45:19 +1300 Subject: [PATCH 300/385] Update db --- composer.lock | 13 +++++++------ .../Http/Databases/Transactions/Update.php | 5 +---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/composer.lock b/composer.lock index d4f1f85151..1afa3f5109 100644 --- a/composer.lock +++ b/composer.lock @@ -4566,16 +4566,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.13", + "version": "0.18.14", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "3d8ce53ae042173bf230445e996056c5f65ded22" + "reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/3d8ce53ae042173bf230445e996056c5f65ded22", - "reference": "3d8ce53ae042173bf230445e996056c5f65ded22", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/4f14ec952c6f4006dd0613e55bbf7631814fbc00", + "reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00", "shasum": "" }, "require": { @@ -4618,9 +4618,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.13" + "source": "https://github.com/utopia-php/storage/tree/0.18.14" }, - "time": "2025-05-26T13:10:35+00:00" + "time": "2025-10-07T10:21:47+00:00" }, { "name": "utopia-php/swoole", @@ -5127,6 +5127,7 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index e33a731d0e..ec0fab2486 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -263,7 +263,6 @@ class Update extends Action $documentId = $operation['documentId']; $data = $operation['data']; - if ($data instanceof Document) { $data = $data->getArrayCopy(); } @@ -351,9 +350,7 @@ class Update extends Action ->setParam('rowId', $doc->getId()) ->setPayload($payload); - $project = $queueForEvents->getProject(); - $result = $queueForRealtime->from($queueForEvents)->trigger(); - + $queueForRealtime->from($queueForEvents)->trigger(); $queueForFunctions->from($queueForEvents)->trigger(); $queueForWebhooks->from($queueForEvents)->trigger(); } From 432faaf2097640417db473538fa93ce66783a7c5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 9 Oct 2025 08:24:39 +0545 Subject: [PATCH 301/385] Fix: make methods protected for extending --- src/Appwrite/Platform/Workers/StatsUsage.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 4da63b6ae3..65e6698928 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -18,24 +18,24 @@ class StatsUsage extends Action /** * In memory per project metrics calculation */ - private array $stats = []; - private int $lastTriggeredTime = 0; - private int $keys = 0; - private const INFINITY_PERIOD = '_inf_'; - private const BATCH_SIZE_DEVELOPMENT = 1; - private const BATCH_SIZE_PRODUCTION = 10_000; + protected array $stats = []; + protected int $lastTriggeredTime = 0; + protected int $keys = 0; + protected const INFINITY_PERIOD = '_inf_'; + protected const BATCH_SIZE_DEVELOPMENT = 1; + protected const BATCH_SIZE_PRODUCTION = 10_000; /** * Stats for batch write separated per project * @var array */ - private array $projects = []; + protected array $projects = []; /** * Array of stat documents to batch write to logsDB * @var array */ - private array $statDocuments = []; + protected array $statDocuments = []; protected Registry $register; @@ -101,7 +101,7 @@ class StatsUsage extends Action return 'stats-usage'; } - private function getBatchSize(): int + protected function getBatchSize(): int { return System::getEnv('_APP_ENV', 'development') === 'development' ? self::BATCH_SIZE_DEVELOPMENT @@ -195,7 +195,7 @@ class StatsUsage extends Action * @param callable(): Database $getProjectDB * @return void */ - private function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void + protected function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void { $dbForProject = $getProjectDB($project); From 3d3f50064ddb4f520bd627c4181cbedb0bb57a31 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 15:50:18 +1300 Subject: [PATCH 302/385] Force set state on increment --- .../Http/Databases/Transactions/Update.php | 4 +- .../Transactions/TransactionsBase.php | 440 ++++++++++++++++++ 2 files changed, 442 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index ec0fab2486..311a5c6e7a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -573,8 +573,8 @@ class Update extends Action return; } - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data) { - $dbForProject->increaseDocumentAttribute( + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, attribute: $data[$this->getAttributeKey()], diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index e50bb9f2bf..dc18338aa0 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -3732,6 +3732,446 @@ trait TransactionsBase $this->assertEquals(0, $row['body']['score']); } + /** + * Test increment followed by update (read-your-writes) + * This test ensures that after an increment operation, subsequent operations + * in the same transaction can see the incremented value in the transaction state. + */ + public function testIncrementThenUpdate(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IncrementUpdateTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'CounterTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'default' => 0, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + // Create initial row + $row = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'test_row', + 'data' => [ + 'counter' => 10, + 'status' => 'initial' + ] + ]); + + $this->assertEquals(201, $row['headers']['status-code']); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Add operations: increment then update + // The update operation needs to see the document in transaction state + // to properly merge the changes + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'increment', + 'rowId' => 'test_row', + 'data' => [ + 'column' => 'counter', + 'value' => 5, + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'update', + 'rowId' => 'test_row', + 'data' => [ + 'status' => 'updated' + ] + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify final values - both increment and update should be applied + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/test_row", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $row['headers']['status-code']); + $this->assertEquals(15, $row['body']['counter'], 'Counter should be incremented: 10 + 5 = 15'); + $this->assertEquals('updated', $row['body']['status'], 'Status should be updated'); + } + + public function testBulkUpdateWithDependentDocuments(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpdateDependentDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'TestTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Create a document, then bulk update it - this triggers the state structure bug + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'create', + 'rowId' => 'doc1', + 'data' => [ + 'status' => 'pending' + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkUpdate', + 'data' => [ + 'queries' => [], + 'data' => [ + 'status' => 'approved' + ] + ] + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code'], 'Bulk update should succeed on dependent documents'); + + // Verify the document was updated + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $row['headers']['status-code']); + $this->assertEquals('approved', $row['body']['status']); + } + + /** + * Test bulk delete with dependent documents (Bug #2 regression test) + */ + public function testBulkDeleteWithDependentDocuments(): void + { + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkDeleteDependentDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'TestTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Create then bulk delete + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'create', + 'rowId' => 'doc1', + 'data' => [ + 'name' => 'Test' + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkDelete', + 'data' => [ + 'queries' => [], + ] + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code'], 'Bulk delete should succeed on dependent documents'); + + // Verify document was deleted + $rows = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(0, $rows['body']['total']); + } + + /** + * Test bulk upsert with dependent documents (Bug #3 regression test) + */ + public function testBulkUpsertWithDependentDocuments(): void + { + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'BulkUpsertDependentDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'TestTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + + sleep(2); + + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Create then bulk upsert same document + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/transactions/{$transactionId}/operations", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'operations' => [ + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'create', + 'rowId' => 'doc1', + 'data' => [ + 'status' => 'pending' + ] + ], + [ + 'databaseId' => $databaseId, + 'tableId' => $tableId, + 'action' => 'bulkUpsert', + 'data' => [ + [ + '$id' => 'doc1', + 'status' => 'approved' + ] + ] + ], + ] + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code'], 'Bulk upsert should succeed on dependent documents'); + + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/doc1", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $row['headers']['status-code']); + $this->assertEquals('approved', $row['body']['status']); + } + /** * Test bulk update operations in transaction */ From 8193f0fcac1db1c8ca74a66fce53a7d80d39a8ed Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 16:24:25 +1300 Subject: [PATCH 303/385] Ensure create/upsert stores in state by generated ID not unique string --- .../Http/Databases/Transactions/Update.php | 8 +- .../Transactions/TransactionsBase.php | 139 ++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 311a5c6e7a..5d29bba34b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -403,10 +403,11 @@ class Update extends Action $data['$id'] = $documentId; } $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { - $state[$collectionId][$data['$id']] = $dbForProject->createDocument( + $doc = $dbForProject->createDocument( $collectionId, new Document($data), ); + $state[$collectionId][$doc->getId()] = $doc; }); } @@ -493,11 +494,12 @@ class Update extends Action return; } - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { - $state[$collectionId][$documentId] = $dbForProject->upsertDocument( + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { + $doc = $dbForProject->upsertDocument( $collectionId, new Document($data), ); + $state[$collectionId][$doc->getId()] = $doc; }); } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index dc18338aa0..b64d249e97 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -5176,4 +5176,143 @@ trait TransactionsBase $this->assertEquals('processed', $response['body']['flag'], 'Bulk update should have matched document created in same transaction'); } } + + /** + * Test upsert with auto-generated ID followed by update + * This tests that the transaction state properly stores the document under its actual ID, + * not under null when the ID is auto-generated + */ + public function testUpsertAutoIdThenUpdate(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'UpsertAutoIDTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'TestCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Create columns + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/string", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + + sleep(3); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // First create a document in the transaction + $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Initial document', + 'counter' => 5 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $docId = $response['body']['$id']; + + // Now upsert the same document using ID::unique() in the path + // The database will recognize it exists and update it, generating a new auto ID if needed + // This tests that handleUpsertOperation properly captures the actual document ID + $response = $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/{$docId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'name' => 'Upserted in transaction', + 'counter' => 10 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Now try to update the same document again in the same transaction + // This verifies that the upsert properly stored the document under its actual ID in state + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/{$docId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'data' => [ + 'name' => 'Updated after upsert', + 'counter' => 20 + ], + 'transactionId' => $transactionId + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Commit transaction + $response = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'commit' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Verify the document has the final updated values + $response = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/{$docId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Updated after upsert', $response['body']['name']); + $this->assertEquals(20, $response['body']['counter']); + } } From 3b225b8ec31827829ff7674ad005e0bac44da284 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 16:52:40 +1300 Subject: [PATCH 304/385] Update specs --- app/config/specs/open-api3-1.8.x-client.json | 4 ++-- app/config/specs/open-api3-1.8.x-console.json | 4 ++-- app/config/specs/open-api3-1.8.x-server.json | 4 ++-- app/config/specs/open-api3-latest-client.json | 4 ++-- app/config/specs/open-api3-latest-console.json | 4 ++-- app/config/specs/open-api3-latest-server.json | 4 ++-- app/config/specs/swagger2-1.8.x-client.json | 4 ++-- app/config/specs/swagger2-1.8.x-console.json | 4 ++-- app/config/specs/swagger2-1.8.x-server.json | 4 ++-- app/config/specs/swagger2-latest-client.json | 4 ++-- app/config/specs/swagger2-latest-console.json | 4 ++-- app/config/specs/swagger2-latest-server.json | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index 87897125dc..bf9f8bbece 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -4942,7 +4942,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5075,7 +5075,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index a389651dd1..73b8ebb1de 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -5341,7 +5341,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5474,7 +5474,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index 497912cbff..b345a36501 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -4883,7 +4883,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5020,7 +5020,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 87897125dc..bf9f8bbece 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -4942,7 +4942,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5075,7 +5075,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index a389651dd1..73b8ebb1de 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -5341,7 +5341,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5474,7 +5474,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 497912cbff..b345a36501 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -4883,7 +4883,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", @@ -5020,7 +5020,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client", diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 102f0357e1..756d19fd53 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -5084,7 +5084,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5217,7 +5217,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index 85a62cffce..df5d64cb8d 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -5503,7 +5503,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5636,7 +5636,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 64f94cb1e6..345b787f79 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -5033,7 +5033,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5170,7 +5170,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 102f0357e1..756d19fd53 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -5084,7 +5084,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5217,7 +5217,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 85a62cffce..df5d64cb8d 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -5503,7 +5503,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5636,7 +5636,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 64f94cb1e6..345b787f79 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -5033,7 +5033,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" @@ -5170,7 +5170,7 @@ "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", - "scope": "transactions.read", + "scope": "rows.read", "platforms": [ "server", "client" From 0877aa2964f88f0560cb023e3c295842fed32a10 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 21:04:50 +1300 Subject: [PATCH 305/385] Generate SDKs --- app/config/platforms.php | 34 +++++++++---------- .../java/databases/create-document.md | 1 + .../java/databases/create-operations.md | 33 ++++++++++++++++++ .../java/databases/create-transaction.md | 22 ++++++++++++ .../databases/decrement-document-attribute.md | 1 + .../java/databases/delete-document.md | 1 + .../java/databases/delete-transaction.md | 22 ++++++++++++ .../java/databases/get-document.md | 1 + .../java/databases/get-transaction.md | 22 ++++++++++++ .../databases/increment-document-attribute.md | 1 + .../java/databases/list-documents.md | 1 + .../java/databases/list-transactions.md | 22 ++++++++++++ .../java/databases/update-document.md | 1 + .../java/databases/update-transaction.md | 24 +++++++++++++ .../java/databases/upsert-document.md | 1 + .../java/tablesdb/create-operations.md | 33 ++++++++++++++++++ .../java/tablesdb/create-row.md | 1 + .../java/tablesdb/create-transaction.md | 22 ++++++++++++ .../java/tablesdb/decrement-row-column.md | 1 + .../java/tablesdb/delete-row.md | 1 + .../java/tablesdb/delete-transaction.md | 22 ++++++++++++ .../client-android/java/tablesdb/get-row.md | 1 + .../java/tablesdb/get-transaction.md | 22 ++++++++++++ .../java/tablesdb/increment-row-column.md | 1 + .../client-android/java/tablesdb/list-rows.md | 1 + .../java/tablesdb/list-transactions.md | 22 ++++++++++++ .../java/tablesdb/update-row.md | 1 + .../java/tablesdb/update-transaction.md | 24 +++++++++++++ .../java/tablesdb/upsert-row.md | 1 + .../kotlin/databases/create-document.md | 1 + .../kotlin/databases/create-operations.md | 24 +++++++++++++ .../kotlin/databases/create-transaction.md | 13 +++++++ .../databases/decrement-document-attribute.md | 1 + .../kotlin/databases/delete-document.md | 1 + .../kotlin/databases/delete-transaction.md | 13 +++++++ .../kotlin/databases/get-document.md | 1 + .../kotlin/databases/get-transaction.md | 13 +++++++ .../databases/increment-document-attribute.md | 1 + .../kotlin/databases/list-documents.md | 1 + .../kotlin/databases/list-transactions.md | 13 +++++++ .../kotlin/databases/update-document.md | 1 + .../kotlin/databases/update-transaction.md | 15 ++++++++ .../kotlin/databases/upsert-document.md | 1 + .../kotlin/tablesdb/create-operations.md | 24 +++++++++++++ .../kotlin/tablesdb/create-row.md | 1 + .../kotlin/tablesdb/create-transaction.md | 13 +++++++ .../kotlin/tablesdb/decrement-row-column.md | 1 + .../kotlin/tablesdb/delete-row.md | 1 + .../kotlin/tablesdb/delete-transaction.md | 13 +++++++ .../client-android/kotlin/tablesdb/get-row.md | 1 + .../kotlin/tablesdb/get-transaction.md | 13 +++++++ .../kotlin/tablesdb/increment-row-column.md | 1 + .../kotlin/tablesdb/list-rows.md | 1 + .../kotlin/tablesdb/list-transactions.md | 13 +++++++ .../kotlin/tablesdb/update-row.md | 1 + .../kotlin/tablesdb/update-transaction.md | 15 ++++++++ .../kotlin/tablesdb/upsert-row.md | 1 + .../examples/databases/create-document.md | 3 +- .../examples/databases/create-operations.md | 23 +++++++++++++ .../examples/databases/create-transaction.md | 12 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-transaction.md | 12 +++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 12 +++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 12 +++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-transaction.md | 14 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/tablesdb/create-operations.md | 23 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-transaction.md | 12 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-transaction.md | 12 +++++++ .../client-apple/examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 12 +++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 12 +++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-transaction.md | 14 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/databases/create-document.md | 1 + .../examples/databases/create-operations.md | 22 ++++++++++++ .../examples/databases/create-transaction.md | 11 ++++++ .../databases/decrement-document-attribute.md | 1 + .../examples/databases/delete-document.md | 1 + .../examples/databases/delete-transaction.md | 11 ++++++ .../examples/databases/get-document.md | 1 + .../examples/databases/get-transaction.md | 11 ++++++ .../databases/increment-document-attribute.md | 1 + .../examples/databases/list-documents.md | 1 + .../examples/databases/list-transactions.md | 11 ++++++ .../examples/databases/update-document.md | 1 + .../examples/databases/update-transaction.md | 13 +++++++ .../examples/databases/upsert-document.md | 1 + .../examples/tablesdb/create-operations.md | 22 ++++++++++++ .../examples/tablesdb/create-row.md | 1 + .../examples/tablesdb/create-transaction.md | 11 ++++++ .../examples/tablesdb/decrement-row-column.md | 1 + .../examples/tablesdb/delete-row.md | 1 + .../examples/tablesdb/delete-transaction.md | 11 ++++++ .../examples/tablesdb/get-row.md | 1 + .../examples/tablesdb/get-transaction.md | 11 ++++++ .../examples/tablesdb/increment-row-column.md | 1 + .../examples/tablesdb/list-rows.md | 1 + .../examples/tablesdb/list-transactions.md | 11 ++++++ .../examples/tablesdb/update-row.md | 1 + .../examples/tablesdb/update-transaction.md | 13 +++++++ .../examples/tablesdb/upsert-row.md | 1 + .../examples/databases/create-document.md | 3 +- .../examples/databases/create-operations.md | 23 +++++++++++++ .../examples/databases/create-transaction.md | 12 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-transaction.md | 7 ++++ .../examples/databases/get-transaction.md | 0 .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-transactions.md | 0 .../examples/databases/update-document.md | 3 +- .../examples/databases/update-transaction.md | 14 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/tablesdb/create-operations.md | 23 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-transaction.md | 12 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-transaction.md | 7 ++++ .../examples/tablesdb/get-transaction.md | 0 .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-transactions.md | 0 .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-transaction.md | 14 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-operations.md | 24 +++++++++++++ .../examples/databases/create-transaction.md | 13 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-transaction.md | 13 +++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 13 +++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 13 +++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-transaction.md | 15 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/tablesdb/create-operations.md | 24 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-transaction.md | 13 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-transaction.md | 13 +++++++ .../examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 13 +++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 13 +++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-transaction.md | 15 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-operations.md | 21 ++++++++++++ .../examples/databases/create-transaction.md | 11 ++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 ++ .../examples/databases/delete-transaction.md | 8 +++++ .../examples/databases/get-transaction.md | 6 ++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-transactions.md | 6 ++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-transaction.md | 12 +++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/tablesdb/create-operations.md | 21 ++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-transaction.md | 11 ++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 ++ .../examples/tablesdb/delete-transaction.md | 8 +++++ .../examples/tablesdb/get-transaction.md | 6 ++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-transactions.md | 6 ++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-transaction.md | 12 +++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/databases/create-operations.md | 2 ++ .../examples/databases/create-transaction.md | 1 + .../examples/databases/delete-transaction.md | 2 ++ .../examples/databases/get-transaction.md | 2 ++ .../examples/databases/list-transactions.md | 1 + .../examples/databases/update-transaction.md | 2 ++ .../migrations/create-csv-migration.md | 2 +- .../examples/tablesdb/create-operations.md | 2 ++ .../examples/tablesdb/create-transaction.md | 1 + .../examples/tablesdb/delete-transaction.md | 2 ++ .../examples/tablesdb/get-transaction.md | 2 ++ .../examples/tablesdb/list-transactions.md | 1 + .../examples/tablesdb/update-transaction.md | 2 ++ .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 24 +++++++++++++ .../examples/databases/create-transaction.md | 13 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 13 +++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 13 +++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 13 +++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 15 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../migrations/create-csv-migration.md | 2 +- .../examples/tablesdb/create-operations.md | 24 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 13 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 13 +++++++ .../console-web/examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 13 +++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 13 +++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 15 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 1 + .../examples/databases/create-documents.md | 1 + .../examples/databases/create-operations.md | 23 +++++++++++++ .../examples/databases/create-transaction.md | 12 +++++++ .../databases/decrement-document-attribute.md | 1 + .../examples/databases/delete-document.md | 1 + .../examples/databases/delete-documents.md | 1 + .../examples/databases/delete-transaction.md | 12 +++++++ .../examples/databases/get-document.md | 1 + .../examples/databases/get-transaction.md | 12 +++++++ .../databases/increment-document-attribute.md | 1 + .../examples/databases/list-documents.md | 1 + .../examples/databases/list-transactions.md | 12 +++++++ .../examples/databases/update-document.md | 1 + .../examples/databases/update-documents.md | 1 + .../examples/databases/update-transaction.md | 14 ++++++++ .../examples/databases/upsert-document.md | 1 + .../examples/databases/upsert-documents.md | 1 + .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 23 +++++++++++++ .../examples/tablesdb/create-row.md | 1 + .../examples/tablesdb/create-rows.md | 1 + .../examples/tablesdb/create-transaction.md | 12 +++++++ .../examples/tablesdb/decrement-row-column.md | 1 + .../examples/tablesdb/delete-row.md | 1 + .../examples/tablesdb/delete-rows.md | 1 + .../examples/tablesdb/delete-transaction.md | 12 +++++++ .../server-dart/examples/tablesdb/get-row.md | 1 + .../examples/tablesdb/get-transaction.md | 12 +++++++ .../examples/tablesdb/increment-row-column.md | 1 + .../examples/tablesdb/list-rows.md | 1 + .../examples/tablesdb/list-transactions.md | 12 +++++++ .../examples/tablesdb/update-row.md | 1 + .../examples/tablesdb/update-rows.md | 1 + .../examples/tablesdb/update-transaction.md | 14 ++++++++ .../examples/tablesdb/upsert-row.md | 1 + .../examples/tablesdb/upsert-rows.md | 1 + .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 25 ++++++++++++++ .../examples/databases/create-transaction.md | 14 ++++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 14 ++++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 14 ++++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 14 ++++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 16 +++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 25 ++++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 14 ++++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 14 ++++++++ .../examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 14 ++++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 14 ++++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 16 +++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 1 + .../examples/databases/create-documents.md | 1 + .../examples/databases/create-operations.md | 30 ++++++++++++++++ .../examples/databases/create-transaction.md | 19 +++++++++++ .../databases/decrement-document-attribute.md | 1 + .../examples/databases/delete-document.md | 1 + .../examples/databases/delete-documents.md | 1 + .../examples/databases/delete-transaction.md | 19 +++++++++++ .../examples/databases/get-document.md | 1 + .../examples/databases/get-transaction.md | 19 +++++++++++ .../databases/increment-document-attribute.md | 1 + .../examples/databases/list-documents.md | 1 + .../examples/databases/list-transactions.md | 19 +++++++++++ .../examples/databases/update-document.md | 1 + .../examples/databases/update-documents.md | 1 + .../examples/databases/update-transaction.md | 21 ++++++++++++ .../examples/databases/upsert-document.md | 1 + .../examples/databases/upsert-documents.md | 1 + .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 30 ++++++++++++++++ .../server-go/examples/tablesdb/create-row.md | 1 + .../examples/tablesdb/create-rows.md | 1 + .../examples/tablesdb/create-transaction.md | 19 +++++++++++ .../examples/tablesdb/decrement-row-column.md | 1 + .../server-go/examples/tablesdb/delete-row.md | 1 + .../examples/tablesdb/delete-rows.md | 1 + .../examples/tablesdb/delete-transaction.md | 19 +++++++++++ .../server-go/examples/tablesdb/get-row.md | 1 + .../examples/tablesdb/get-transaction.md | 19 +++++++++++ .../examples/tablesdb/increment-row-column.md | 1 + .../server-go/examples/tablesdb/list-rows.md | 1 + .../examples/tablesdb/list-transactions.md | 19 +++++++++++ .../server-go/examples/tablesdb/update-row.md | 1 + .../examples/tablesdb/update-rows.md | 1 + .../examples/tablesdb/update-transaction.md | 21 ++++++++++++ .../server-go/examples/tablesdb/upsert-row.md | 1 + .../examples/tablesdb/upsert-rows.md | 1 + .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 23 +++++++++++++ .../examples/databases/create-transaction.md | 12 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 7 ++++ .../examples/databases/get-transaction.md | 0 .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-transactions.md | 0 .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 14 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 23 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 12 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 7 ++++ .../examples/tablesdb/get-transaction.md | 0 .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-transactions.md | 0 .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 14 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../java/databases/create-document.md | 1 + .../java/databases/create-documents.md | 1 + .../java/databases/create-operations.md | 34 +++++++++++++++++++ .../java/databases/create-transaction.md | 23 +++++++++++++ .../databases/decrement-document-attribute.md | 1 + .../java/databases/delete-document.md | 1 + .../java/databases/delete-documents.md | 1 + .../java/databases/delete-transaction.md | 23 +++++++++++++ .../java/databases/get-document.md | 1 + .../java/databases/get-transaction.md | 23 +++++++++++++ .../databases/increment-document-attribute.md | 1 + .../java/databases/list-documents.md | 1 + .../java/databases/list-transactions.md | 23 +++++++++++++ .../java/databases/update-document.md | 1 + .../java/databases/update-documents.md | 1 + .../java/databases/update-transaction.md | 25 ++++++++++++++ .../java/databases/upsert-document.md | 1 + .../java/databases/upsert-documents.md | 1 + .../java/messaging/create-push.md | 2 +- .../java/messaging/update-push.md | 2 +- .../java/tablesdb/create-operations.md | 34 +++++++++++++++++++ .../server-kotlin/java/tablesdb/create-row.md | 1 + .../java/tablesdb/create-rows.md | 1 + .../java/tablesdb/create-transaction.md | 23 +++++++++++++ .../java/tablesdb/decrement-row-column.md | 1 + .../server-kotlin/java/tablesdb/delete-row.md | 1 + .../java/tablesdb/delete-rows.md | 1 + .../java/tablesdb/delete-transaction.md | 23 +++++++++++++ .../server-kotlin/java/tablesdb/get-row.md | 1 + .../java/tablesdb/get-transaction.md | 23 +++++++++++++ .../java/tablesdb/increment-row-column.md | 1 + .../server-kotlin/java/tablesdb/list-rows.md | 1 + .../java/tablesdb/list-transactions.md | 23 +++++++++++++ .../server-kotlin/java/tablesdb/update-row.md | 1 + .../java/tablesdb/update-rows.md | 1 + .../java/tablesdb/update-transaction.md | 25 ++++++++++++++ .../server-kotlin/java/tablesdb/upsert-row.md | 1 + .../java/tablesdb/upsert-rows.md | 1 + .../kotlin/databases/create-document.md | 3 +- .../kotlin/databases/create-documents.md | 3 +- .../kotlin/databases/create-operations.md | 25 ++++++++++++++ .../kotlin/databases/create-transaction.md | 14 ++++++++ .../databases/decrement-document-attribute.md | 3 +- .../kotlin/databases/delete-document.md | 3 +- .../kotlin/databases/delete-documents.md | 3 +- .../kotlin/databases/delete-transaction.md | 14 ++++++++ .../kotlin/databases/get-document.md | 3 +- .../kotlin/databases/get-transaction.md | 14 ++++++++ .../databases/increment-document-attribute.md | 3 +- .../kotlin/databases/list-documents.md | 3 +- .../kotlin/databases/list-transactions.md | 14 ++++++++ .../kotlin/databases/update-document.md | 3 +- .../kotlin/databases/update-documents.md | 3 +- .../kotlin/databases/update-transaction.md | 16 +++++++++ .../kotlin/databases/upsert-document.md | 3 +- .../kotlin/databases/upsert-documents.md | 3 +- .../kotlin/messaging/create-push.md | 2 +- .../kotlin/messaging/update-push.md | 2 +- .../kotlin/tablesdb/create-operations.md | 25 ++++++++++++++ .../kotlin/tablesdb/create-row.md | 3 +- .../kotlin/tablesdb/create-rows.md | 3 +- .../kotlin/tablesdb/create-transaction.md | 14 ++++++++ .../kotlin/tablesdb/decrement-row-column.md | 3 +- .../kotlin/tablesdb/delete-row.md | 3 +- .../kotlin/tablesdb/delete-rows.md | 3 +- .../kotlin/tablesdb/delete-transaction.md | 14 ++++++++ .../server-kotlin/kotlin/tablesdb/get-row.md | 3 +- .../kotlin/tablesdb/get-transaction.md | 14 ++++++++ .../kotlin/tablesdb/increment-row-column.md | 3 +- .../kotlin/tablesdb/list-rows.md | 3 +- .../kotlin/tablesdb/list-transactions.md | 14 ++++++++ .../kotlin/tablesdb/update-row.md | 3 +- .../kotlin/tablesdb/update-rows.md | 3 +- .../kotlin/tablesdb/update-transaction.md | 16 +++++++++ .../kotlin/tablesdb/upsert-row.md | 3 +- .../kotlin/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 26 ++++++++++++++ .../examples/databases/create-transaction.md | 15 ++++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 15 ++++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 15 ++++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 15 ++++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 17 ++++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 26 ++++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 15 ++++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 15 ++++++++ .../server-php/examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 15 ++++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../server-php/examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 15 ++++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 17 ++++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 24 +++++++++++++ .../examples/databases/create-transaction.md | 13 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 13 +++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 13 +++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 13 +++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 15 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 24 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 13 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 13 +++++++ .../examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 13 +++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 13 +++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 15 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 22 ++++++++++++ .../examples/databases/create-transaction.md | 12 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 ++ .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 9 +++++ .../examples/databases/get-transaction.md | 7 ++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-transactions.md | 7 ++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 13 +++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 22 ++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 12 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 ++ .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 9 +++++ .../examples/tablesdb/get-transaction.md | 7 ++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-transactions.md | 7 ++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 13 +++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 25 ++++++++++++++ .../examples/databases/create-transaction.md | 14 ++++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 14 ++++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 14 ++++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 14 ++++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 16 +++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 25 ++++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 14 ++++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 14 ++++++++ .../server-ruby/examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 14 ++++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 14 ++++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 16 +++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- .../examples/databases/create-document.md | 3 +- .../examples/databases/create-documents.md | 3 +- .../examples/databases/create-operations.md | 24 +++++++++++++ .../examples/databases/create-transaction.md | 13 +++++++ .../databases/decrement-document-attribute.md | 3 +- .../examples/databases/delete-document.md | 3 +- .../examples/databases/delete-documents.md | 3 +- .../examples/databases/delete-transaction.md | 13 +++++++ .../examples/databases/get-document.md | 3 +- .../examples/databases/get-transaction.md | 13 +++++++ .../databases/increment-document-attribute.md | 3 +- .../examples/databases/list-documents.md | 3 +- .../examples/databases/list-transactions.md | 13 +++++++ .../examples/databases/update-document.md | 3 +- .../examples/databases/update-documents.md | 3 +- .../examples/databases/update-transaction.md | 15 ++++++++ .../examples/databases/upsert-document.md | 3 +- .../examples/databases/upsert-documents.md | 3 +- .../examples/messaging/create-push.md | 2 +- .../examples/messaging/update-push.md | 2 +- .../examples/tablesdb/create-operations.md | 24 +++++++++++++ .../examples/tablesdb/create-row.md | 3 +- .../examples/tablesdb/create-rows.md | 3 +- .../examples/tablesdb/create-transaction.md | 13 +++++++ .../examples/tablesdb/decrement-row-column.md | 3 +- .../examples/tablesdb/delete-row.md | 3 +- .../examples/tablesdb/delete-rows.md | 3 +- .../examples/tablesdb/delete-transaction.md | 13 +++++++ .../server-swift/examples/tablesdb/get-row.md | 3 +- .../examples/tablesdb/get-transaction.md | 13 +++++++ .../examples/tablesdb/increment-row-column.md | 3 +- .../examples/tablesdb/list-rows.md | 3 +- .../examples/tablesdb/list-transactions.md | 13 +++++++ .../examples/tablesdb/update-row.md | 3 +- .../examples/tablesdb/update-rows.md | 3 +- .../examples/tablesdb/update-transaction.md | 15 ++++++++ .../examples/tablesdb/upsert-row.md | 3 +- .../examples/tablesdb/upsert-rows.md | 3 +- docs/sdks/android/CHANGELOG.md | 4 +++ docs/sdks/apple/CHANGELOG.md | 4 +++ docs/sdks/cli/CHANGELOG.md | 4 +++ docs/sdks/dart/CHANGELOG.md | 4 +++ docs/sdks/dotnet/CHANGELOG.md | 4 +++ docs/sdks/flutter/CHANGELOG.md | 4 +++ docs/sdks/go/CHANGELOG.md | 4 +++ docs/sdks/kotlin/CHANGELOG.md | 4 +++ docs/sdks/nodejs/CHANGELOG.md | 4 +++ docs/sdks/php/CHANGELOG.md | 4 +++ docs/sdks/python/CHANGELOG.md | 4 +++ docs/sdks/react-native/CHANGELOG.md | 4 +++ docs/sdks/ruby/CHANGELOG.md | 4 +++ docs/sdks/swift/CHANGELOG.md | 4 +++ docs/sdks/web/CHANGELOG.md | 4 +++ 666 files changed, 4309 insertions(+), 303 deletions(-) create mode 100644 docs/examples/1.8.x/client-android/java/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-android/java/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-android/java/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-android/java/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-android/kotlin/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-apple/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-apple/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-flutter/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-graphql/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-react-native/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/client-rest/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/client-rest/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/console-cli/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/console-cli/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/console-web/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/console-web/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-dart/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-dart/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-go/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-go/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-graphql/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-kotlin/java/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-php/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-php/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-python/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-python/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-rest/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-rest/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-ruby/examples/tablesdb/update-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/create-operations.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/create-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/get-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/list-transactions.md create mode 100644 docs/examples/1.8.x/server-swift/examples/databases/update-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/create-operations.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/create-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/delete-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/get-transaction.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/list-transactions.md create mode 100644 docs/examples/1.8.x/server-swift/examples/tablesdb/update-transaction.md diff --git a/app/config/platforms.php b/app/config/platforms.php index 9ea8fc4df4..0f32c9f45c 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '21.1.0', + 'version' => '21.2.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -24,7 +24,7 @@ return [ 'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git', 'gitRepoName' => 'sdk-for-web', 'gitUserName' => 'appwrite', - 'gitBranch' => 'feat-txn', + 'gitBranch' => 'dev', 'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'), 'demos' => [ [ @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '20.1.0', + 'version' => '20.2.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '13.1.0', + 'version' => '13.2.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '11.1.0', + 'version' => '11.2.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -139,7 +139,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.16.0', + 'version' => '0.17.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -226,7 +226,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '10.1.0', + 'version' => '10.2.0', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '20.1.0', + 'version' => '20.2.0', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -275,13 +275,13 @@ return [ 'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git', 'gitRepoName' => 'sdk-for-node', 'gitUserName' => 'appwrite', - 'gitBranch' => 'feat-txn', + 'gitBranch' => 'dev', 'changelog' => \realpath(__DIR__ . '/../../docs/sdks/nodejs/CHANGELOG.md'), ], [ 'key' => 'php', 'name' => 'PHP', - 'version' => '17.3.0', + 'version' => '17.4.0', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -300,7 +300,7 @@ return [ [ 'key' => 'python', 'name' => 'Python', - 'version' => '13.3.0', + 'version' => '13.4.0', 'url' => 'https://github.com/appwrite/sdk-for-python', 'package' => 'https://pypi.org/project/appwrite/', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '19.1.0', + 'version' => '19.2.0', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => 'v0.12.0', + 'version' => 'v0.13.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.20.0', + 'version' => '0.21.0', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '19.1.0', + 'version' => '19.2.0', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '12.1.0', + 'version' => '12.2.0', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '13.1.0', + 'version' => '13.2.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/docs/examples/1.8.x/client-android/java/databases/create-document.md b/docs/examples/1.8.x/client-android/java/databases/create-document.md index bd0b57ac4c..694d99a089 100644 --- a/docs/examples/1.8.x/client-android/java/databases/create-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/create-document.md @@ -20,6 +20,7 @@ databases.createDocument( "isAdmin" to false ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/create-operations.md b/docs/examples/1.8.x/client-android/java/databases/create-operations.md new file mode 100644 index 0000000000..def58af773 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/create-operations.md @@ -0,0 +1,33 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.createOperations( + "", // transactionId + listOf( + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/create-transaction.md b/docs/examples/1.8.x/client-android/java/databases/create-transaction.md new file mode 100644 index 0000000000..871d1907f6 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/create-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-android/java/databases/decrement-document-attribute.md index de6a4ab48d..8669494532 100644 --- a/docs/examples/1.8.x/client-android/java/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-android/java/databases/decrement-document-attribute.md @@ -15,6 +15,7 @@ databases.decrementDocumentAttribute( "", // attribute 0, // value (optional) 0, // min (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/delete-document.md b/docs/examples/1.8.x/client-android/java/databases/delete-document.md index 5288e53bed..2795670807 100644 --- a/docs/examples/1.8.x/client-android/java/databases/delete-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/delete-document.md @@ -12,6 +12,7 @@ databases.deleteDocument( "", // databaseId "", // collectionId "", // documentId + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/delete-transaction.md b/docs/examples/1.8.x/client-android/java/databases/delete-transaction.md new file mode 100644 index 0000000000..ac1121e9b9 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/delete-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/get-document.md b/docs/examples/1.8.x/client-android/java/databases/get-document.md index e7ae2079ed..92d6b5ed23 100644 --- a/docs/examples/1.8.x/client-android/java/databases/get-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/get-document.md @@ -13,6 +13,7 @@ databases.getDocument( "", // collectionId "", // documentId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/get-transaction.md b/docs/examples/1.8.x/client-android/java/databases/get-transaction.md new file mode 100644 index 0000000000..5dee850305 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/get-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-android/java/databases/increment-document-attribute.md index 94ffa9d749..5928b455c6 100644 --- a/docs/examples/1.8.x/client-android/java/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-android/java/databases/increment-document-attribute.md @@ -15,6 +15,7 @@ databases.incrementDocumentAttribute( "", // attribute 0, // value (optional) 0, // max (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/list-documents.md b/docs/examples/1.8.x/client-android/java/databases/list-documents.md index 606d67705d..7b2ba23453 100644 --- a/docs/examples/1.8.x/client-android/java/databases/list-documents.md +++ b/docs/examples/1.8.x/client-android/java/databases/list-documents.md @@ -12,6 +12,7 @@ databases.listDocuments( "", // databaseId "", // collectionId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/list-transactions.md b/docs/examples/1.8.x/client-android/java/databases/list-transactions.md new file mode 100644 index 0000000000..39f58f3490 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/list-transactions.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.listTransactions( + listOf(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/update-document.md b/docs/examples/1.8.x/client-android/java/databases/update-document.md index baa827cdf5..a6103e44d1 100644 --- a/docs/examples/1.8.x/client-android/java/databases/update-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/update-document.md @@ -14,6 +14,7 @@ databases.updateDocument( "", // documentId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/update-transaction.md b/docs/examples/1.8.x/client-android/java/databases/update-transaction.md new file mode 100644 index 0000000000..c5f586fce3 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/databases/update-transaction.md @@ -0,0 +1,24 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Databases databases = new Databases(client); + +databases.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/databases/upsert-document.md b/docs/examples/1.8.x/client-android/java/databases/upsert-document.md index 868576b982..52be46ebe6 100644 --- a/docs/examples/1.8.x/client-android/java/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/upsert-document.md @@ -14,6 +14,7 @@ databases.upsertDocument( "", // documentId mapOf( "a" to "b" ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md b/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md new file mode 100644 index 0000000000..7ca288c2ae --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md @@ -0,0 +1,33 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.createOperations( + "", // transactionId + listOf( + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md index 8273573ddd..92a9058401 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md @@ -20,6 +20,7 @@ tablesDB.createRow( "isAdmin" to false ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-android/java/tablesdb/create-transaction.md new file mode 100644 index 0000000000..c232c56991 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/create-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-android/java/tablesdb/decrement-row-column.md index 73f2128a8f..2252564e73 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/decrement-row-column.md @@ -15,6 +15,7 @@ tablesDB.decrementRowColumn( "", // column 0, // value (optional) 0, // min (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/delete-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/delete-row.md index d4c351bc32..6680100499 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/delete-row.md @@ -12,6 +12,7 @@ tablesDB.deleteRow( "", // databaseId "", // tableId "", // rowId + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-android/java/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..18cb2357d3 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/delete-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md index 191fc896e9..45efc6b061 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md @@ -13,6 +13,7 @@ tablesDB.getRow( "", // tableId "", // rowId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-android/java/tablesdb/get-transaction.md new file mode 100644 index 0000000000..fb51916264 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/get-transaction.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-android/java/tablesdb/increment-row-column.md index dc2120ea07..a1194a67a5 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/increment-row-column.md @@ -15,6 +15,7 @@ tablesDB.incrementRowColumn( "", // column 0, // value (optional) 0, // max (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md b/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md index dc50574f1e..21f0005b2d 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md @@ -12,6 +12,7 @@ tablesDB.listRows( "", // databaseId "", // tableId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md new file mode 100644 index 0000000000..c37ccf1931 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md @@ -0,0 +1,22 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.listTransactions( + listOf(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md index 0f1fabc91e..cea7baaa16 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md @@ -14,6 +14,7 @@ tablesDB.updateRow( "", // rowId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-android/java/tablesdb/update-transaction.md new file mode 100644 index 0000000000..804b5d5023 --- /dev/null +++ b/docs/examples/1.8.x/client-android/java/tablesdb/update-transaction.md @@ -0,0 +1,24 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md index 22c0e94b44..9d6323fffa 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md @@ -14,6 +14,7 @@ tablesDB.upsertRow( "", // rowId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/create-document.md b/docs/examples/1.8.x/client-android/kotlin/databases/create-document.md index 7f0aaf81f4..7d4b04d13a 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/create-document.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/create-document.md @@ -20,4 +20,5 @@ val result = databases.createDocument( "isAdmin" to false ), permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md b/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md new file mode 100644 index 0000000000..62c93351e7 --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md @@ -0,0 +1,24 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.createOperations( + transactionId = "", + operations = listOf( + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/create-transaction.md b/docs/examples/1.8.x/client-android/kotlin/databases/create-transaction.md new file mode 100644 index 0000000000..3b953c3bda --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/create-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.createTransaction( + ttl = 60, // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-android/kotlin/databases/decrement-document-attribute.md index c500fa8687..84a4a0edea 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/decrement-document-attribute.md @@ -15,4 +15,5 @@ val result = databases.decrementDocumentAttribute( attribute = "", value = 0, // (optional) min = 0, // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/delete-document.md b/docs/examples/1.8.x/client-android/kotlin/databases/delete-document.md index 052bf97f89..242655ec1f 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/delete-document.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/delete-document.md @@ -12,4 +12,5 @@ val result = databases.deleteDocument( databaseId = "", collectionId = "", documentId = "", + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/delete-transaction.md b/docs/examples/1.8.x/client-android/kotlin/databases/delete-transaction.md new file mode 100644 index 0000000000..bbce98c661 --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/delete-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.deleteTransaction( + transactionId = "", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/get-document.md b/docs/examples/1.8.x/client-android/kotlin/databases/get-document.md index 157af2b562..f21b6f34f0 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/get-document.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/get-document.md @@ -13,4 +13,5 @@ val result = databases.getDocument( collectionId = "", documentId = "", queries = listOf(), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/get-transaction.md b/docs/examples/1.8.x/client-android/kotlin/databases/get-transaction.md new file mode 100644 index 0000000000..0a1fda69df --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/get-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.getTransaction( + transactionId = "", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-android/kotlin/databases/increment-document-attribute.md index 0ae6b02d3d..ca32cfcb5c 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/increment-document-attribute.md @@ -15,4 +15,5 @@ val result = databases.incrementDocumentAttribute( attribute = "", value = 0, // (optional) max = 0, // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/list-documents.md b/docs/examples/1.8.x/client-android/kotlin/databases/list-documents.md index 1cc785fab1..a2b6e0e0b6 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/list-documents.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/list-documents.md @@ -12,4 +12,5 @@ val result = databases.listDocuments( databaseId = "", collectionId = "", queries = listOf(), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/list-transactions.md b/docs/examples/1.8.x/client-android/kotlin/databases/list-transactions.md new file mode 100644 index 0000000000..da5cd4ba7a --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/list-transactions.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.listTransactions( + queries = listOf(), // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/update-document.md b/docs/examples/1.8.x/client-android/kotlin/databases/update-document.md index d61fdea5b1..2cacce6f95 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/update-document.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/update-document.md @@ -14,4 +14,5 @@ val result = databases.updateDocument( documentId = "", data = mapOf( "a" to "b" ), // (optional) permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/update-transaction.md b/docs/examples/1.8.x/client-android/kotlin/databases/update-transaction.md new file mode 100644 index 0000000000..c9d6a852ce --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/databases/update-transaction.md @@ -0,0 +1,15 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val databases = Databases(client) + +val result = databases.updateTransaction( + transactionId = "", + commit = false, // (optional) + rollback = false, // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/upsert-document.md b/docs/examples/1.8.x/client-android/kotlin/databases/upsert-document.md index a31dfc8797..e5ac4375e8 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/upsert-document.md @@ -14,4 +14,5 @@ val result = databases.upsertDocument( documentId = "", data = mapOf( "a" to "b" ), permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md new file mode 100644 index 0000000000..3073a00bca --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.createOperations( + transactionId = "", + operations = listOf( + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-row.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-row.md index eb44cc48d6..f5850b23be 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-row.md @@ -20,4 +20,5 @@ val result = tablesDB.createRow( "isAdmin" to false ), permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-transaction.md new file mode 100644 index 0000000000..b011fa051f --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.createTransaction( + ttl = 60, // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/decrement-row-column.md index 645e9f4a66..1a8964078e 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/decrement-row-column.md @@ -15,4 +15,5 @@ val result = tablesDB.decrementRowColumn( column = "", value = 0, // (optional) min = 0, // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-row.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-row.md index 3fcd409295..6912afa10a 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-row.md @@ -12,4 +12,5 @@ val result = tablesDB.deleteRow( databaseId = "", tableId = "", rowId = "", + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..92c0074acc --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.deleteTransaction( + transactionId = "", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-row.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-row.md index b754cba915..adea429759 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-row.md @@ -13,4 +13,5 @@ val result = tablesDB.getRow( tableId = "", rowId = "", queries = listOf(), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-transaction.md new file mode 100644 index 0000000000..38b7c33e83 --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.getTransaction( + transactionId = "", +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/increment-row-column.md index 230408abac..1b679e181b 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/increment-row-column.md @@ -15,4 +15,5 @@ val result = tablesDB.incrementRowColumn( column = "", value = 0, // (optional) max = 0, // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-rows.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-rows.md index 05d9ca33b3..6d2a4b8b40 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-rows.md @@ -12,4 +12,5 @@ val result = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf(), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-transactions.md new file mode 100644 index 0000000000..b7764c719f --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.listTransactions( + queries = listOf(), // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-row.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-row.md index f99f8f275e..a17f231418 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-row.md @@ -14,4 +14,5 @@ val result = tablesDB.updateRow( rowId = "", data = mapOf( "a" to "b" ), // (optional) permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-transaction.md new file mode 100644 index 0000000000..791649ff09 --- /dev/null +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val tablesDB = TablesDB(client) + +val result = tablesDB.updateTransaction( + transactionId = "", + commit = false, // (optional) + rollback = false, // (optional) +) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/upsert-row.md index d82406a7f8..074fa339cb 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/upsert-row.md @@ -14,4 +14,5 @@ val result = tablesDB.upsertRow( rowId = "", data = mapOf( "a" to "b" ), // (optional) permissions = listOf("read("any")"), // (optional) + transactionId = "", // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-apple/examples/databases/create-document.md b/docs/examples/1.8.x/client-apple/examples/databases/create-document.md index c044eee17a..d7fa796ed1 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/create-document.md @@ -17,6 +17,7 @@ let document = try await databases.createDocument( "age": 30, "isAdmin": false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/create-operations.md b/docs/examples/1.8.x/client-apple/examples/databases/create-operations.md new file mode 100644 index 0000000000..7c22de38ca --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/create-operations.md @@ -0,0 +1,23 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let transaction = try await databases.createOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-apple/examples/databases/create-transaction.md new file mode 100644 index 0000000000..4319907a65 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let transaction = try await databases.createTransaction( + ttl: 60 // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-apple/examples/databases/decrement-document-attribute.md index 8ef2637bf2..714d7baabb 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/decrement-document-attribute.md @@ -12,6 +12,7 @@ let document = try await databases.decrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/delete-document.md b/docs/examples/1.8.x/client-apple/examples/databases/delete-document.md index 301203dc7f..4d8f5074ea 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/delete-document.md @@ -9,6 +9,7 @@ let databases = Databases(client) let result = try await databases.deleteDocument( databaseId: "", collectionId: "", - documentId: "" + documentId: "", + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-apple/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..134d72df9c --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/delete-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let result = try await databases.deleteTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/get-document.md b/docs/examples/1.8.x/client-apple/examples/databases/get-document.md index 6e4dc55c6d..89c89a3868 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/get-document.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/get-document.md @@ -10,6 +10,7 @@ let document = try await databases.getDocument( databaseId: "", collectionId: "", documentId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-apple/examples/databases/get-transaction.md new file mode 100644 index 0000000000..6274ff8d15 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/get-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let transaction = try await databases.getTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-apple/examples/databases/increment-document-attribute.md index f64b2cd76c..9c771a7490 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/increment-document-attribute.md @@ -12,6 +12,7 @@ let document = try await databases.incrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/list-documents.md b/docs/examples/1.8.x/client-apple/examples/databases/list-documents.md index 0d624f3a92..528d9992a4 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/list-documents.md @@ -9,6 +9,7 @@ let databases = Databases(client) let documentList = try await databases.listDocuments( databaseId: "", collectionId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-apple/examples/databases/list-transactions.md new file mode 100644 index 0000000000..3f889c213c --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/list-transactions.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let transactionList = try await databases.listTransactions( + queries: [] // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/update-document.md b/docs/examples/1.8.x/client-apple/examples/databases/update-document.md index af224c8e09..d626d7d3c0 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/update-document.md @@ -11,6 +11,7 @@ let document = try await databases.updateDocument( collectionId: "", documentId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-apple/examples/databases/update-transaction.md new file mode 100644 index 0000000000..96705d019f --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/databases/update-transaction.md @@ -0,0 +1,14 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let databases = Databases(client) + +let transaction = try await databases.updateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-apple/examples/databases/upsert-document.md index 3e1bf83a66..8e2a4a09e9 100644 --- a/docs/examples/1.8.x/client-apple/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-apple/examples/databases/upsert-document.md @@ -11,6 +11,7 @@ let document = try await databases.upsertDocument( collectionId: "", documentId: "", data: [:], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..c4032cc02c --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-operations.md @@ -0,0 +1,23 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.createOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-row.md index 2ee601340f..4d66980bb5 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-row.md @@ -17,6 +17,7 @@ let row = try await tablesDB.createRow( "age": 30, "isAdmin": false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..366aa5b663 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.createTransaction( + ttl: 60 // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/decrement-row-column.md index ab8ac5b0b8..8a41d43362 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/decrement-row-column.md @@ -12,6 +12,7 @@ let row = try await tablesDB.decrementRowColumn( rowId: "", column: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-row.md index b527acaed9..6ddd1c5fc2 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-row.md @@ -9,6 +9,7 @@ let tablesDB = TablesDB(client) let result = try await tablesDB.deleteRow( databaseId: "", tableId: "", - rowId: "" + rowId: "", + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..12cf45fa2c --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/delete-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let result = try await tablesDB.deleteTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/get-row.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/get-row.md index bc2e2c6b55..7a3aa40806 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/get-row.md @@ -10,6 +10,7 @@ let row = try await tablesDB.getRow( databaseId: "", tableId: "", rowId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..fe3cbf78b2 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/get-transaction.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.getTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/increment-row-column.md index 550d6685af..29b346e27d 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/increment-row-column.md @@ -12,6 +12,7 @@ let row = try await tablesDB.incrementRowColumn( rowId: "", column: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/list-rows.md index 94853c24a0..dee2ab9e81 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/list-rows.md @@ -9,6 +9,7 @@ let tablesDB = TablesDB(client) let rowList = try await tablesDB.listRows( databaseId: "", tableId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..b7edd2d1a0 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/list-transactions.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let transactionList = try await tablesDB.listTransactions( + queries: [] // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/update-row.md index 87a8f27313..cd5591db80 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/update-row.md @@ -11,6 +11,7 @@ let row = try await tablesDB.updateRow( tableId: "", rowId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..2c0e6a7a37 --- /dev/null +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/update-transaction.md @@ -0,0 +1,14 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.updateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +) + diff --git a/docs/examples/1.8.x/client-apple/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-apple/examples/tablesdb/upsert-row.md index ed95da7b62..955935adef 100644 --- a/docs/examples/1.8.x/client-apple/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-apple/examples/tablesdb/upsert-row.md @@ -11,6 +11,7 @@ let row = try await tablesDB.upsertRow( tableId: "", rowId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/create-document.md b/docs/examples/1.8.x/client-flutter/examples/databases/create-document.md index 3becbcf1fc..0acbe689dc 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/create-document.md @@ -18,4 +18,5 @@ Document result = await databases.createDocument( "isAdmin": false }, permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/create-operations.md b/docs/examples/1.8.x/client-flutter/examples/databases/create-operations.md new file mode 100644 index 0000000000..2dec7ff7c8 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/create-operations.md @@ -0,0 +1,22 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +Transaction result = await databases.createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ], // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-flutter/examples/databases/create-transaction.md new file mode 100644 index 0000000000..3d7ddc3efa --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/create-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +Transaction result = await databases.createTransaction( + ttl: 60, // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-flutter/examples/databases/decrement-document-attribute.md index ec0d9ee300..dad45bc836 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/decrement-document-attribute.md @@ -13,4 +13,5 @@ Document result = await databases.decrementDocumentAttribute( attribute: '', value: 0, // optional min: 0, // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/delete-document.md b/docs/examples/1.8.x/client-flutter/examples/databases/delete-document.md index 3354917c20..bd101370a6 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/delete-document.md @@ -10,4 +10,5 @@ await databases.deleteDocument( databaseId: '', collectionId: '', documentId: '', + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-flutter/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..333dd1d3a1 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/delete-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +await databases.deleteTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/get-document.md b/docs/examples/1.8.x/client-flutter/examples/databases/get-document.md index f85c1f9b5a..9dcf2cf119 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/get-document.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/get-document.md @@ -11,4 +11,5 @@ Document result = await databases.getDocument( collectionId: '', documentId: '', queries: [], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-flutter/examples/databases/get-transaction.md new file mode 100644 index 0000000000..153b0f3856 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/get-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +Transaction result = await databases.getTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-flutter/examples/databases/increment-document-attribute.md index 78f5b0cb6f..855fd8f51e 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/increment-document-attribute.md @@ -13,4 +13,5 @@ Document result = await databases.incrementDocumentAttribute( attribute: '', value: 0, // optional max: 0, // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/list-documents.md b/docs/examples/1.8.x/client-flutter/examples/databases/list-documents.md index 31fec1f522..b53120cb4e 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/list-documents.md @@ -10,4 +10,5 @@ DocumentList result = await databases.listDocuments( databaseId: '', collectionId: '', queries: [], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-flutter/examples/databases/list-transactions.md new file mode 100644 index 0000000000..467a1ced84 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/list-transactions.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +TransactionList result = await databases.listTransactions( + queries: [], // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/update-document.md b/docs/examples/1.8.x/client-flutter/examples/databases/update-document.md index 1f444d875a..44ade30c3a 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/update-document.md @@ -12,4 +12,5 @@ Document result = await databases.updateDocument( documentId: '', data: {}, // optional permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-flutter/examples/databases/update-transaction.md new file mode 100644 index 0000000000..a51f9d05d6 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/databases/update-transaction.md @@ -0,0 +1,13 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +Databases databases = Databases(client); + +Transaction result = await databases.updateTransaction( + transactionId: '', + commit: false, // optional + rollback: false, // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-flutter/examples/databases/upsert-document.md index 398a99cb1d..10117ac78d 100644 --- a/docs/examples/1.8.x/client-flutter/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-flutter/examples/databases/upsert-document.md @@ -12,4 +12,5 @@ Document result = await databases.upsertDocument( documentId: '', data: {}, permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..631aefe603 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-operations.md @@ -0,0 +1,22 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ], // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-row.md index 038bb2bac6..345f0c239a 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-row.md @@ -18,4 +18,5 @@ Row result = await tablesDB.createRow( "isAdmin": false }, permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..0ad0eb5223 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/create-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.createTransaction( + ttl: 60, // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/decrement-row-column.md index 4f3b4bdb61..65f67513f4 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/decrement-row-column.md @@ -13,4 +13,5 @@ Row result = await tablesDB.decrementRowColumn( column: '', value: 0, // optional min: 0, // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-row.md index cc902fa198..b8ea1d2656 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-row.md @@ -10,4 +10,5 @@ await tablesDB.deleteRow( databaseId: '', tableId: '', rowId: '', + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..2d27c6afda --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/delete-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +await tablesDB.deleteTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-row.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-row.md index 29e6eaab93..eb75da5073 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-row.md @@ -11,4 +11,5 @@ Row result = await tablesDB.getRow( tableId: '', rowId: '', queries: [], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..000e230227 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/get-transaction.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.getTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/increment-row-column.md index e05dc76753..91cd0ce351 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/increment-row-column.md @@ -13,4 +13,5 @@ Row result = await tablesDB.incrementRowColumn( column: '', value: 0, // optional max: 0, // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-rows.md index 7763e2ae3d..01d7066501 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-rows.md @@ -10,4 +10,5 @@ RowList result = await tablesDB.listRows( databaseId: '', tableId: '', queries: [], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..5e088cedc3 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/list-transactions.md @@ -0,0 +1,11 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +TransactionList result = await tablesDB.listTransactions( + queries: [], // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-row.md index c2cc84d7f6..08ab309363 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-row.md @@ -12,4 +12,5 @@ Row result = await tablesDB.updateRow( rowId: '', data: {}, // optional permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..ef56443e99 --- /dev/null +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/update-transaction.md @@ -0,0 +1,13 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.updateTransaction( + transactionId: '', + commit: false, // optional + rollback: false, // optional +); diff --git a/docs/examples/1.8.x/client-flutter/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-flutter/examples/tablesdb/upsert-row.md index 6a958a1898..061bb0b85a 100644 --- a/docs/examples/1.8.x/client-flutter/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-flutter/examples/tablesdb/upsert-row.md @@ -12,4 +12,5 @@ Row result = await tablesDB.upsertRow( rowId: '', data: {}, // optional permissions: ["read("any")"], // optional + transactionId: '', // optional ); diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/create-document.md b/docs/examples/1.8.x/client-graphql/examples/databases/create-document.md index 39e4bba1cb..411615f7a7 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/create-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/create-operations.md b/docs/examples/1.8.x/client-graphql/examples/databases/create-operations.md new file mode 100644 index 0000000000..1be3b39ee1 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/databases/create-operations.md @@ -0,0 +1,23 @@ +mutation { + databasesCreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-graphql/examples/databases/create-transaction.md new file mode 100644 index 0000000000..7fea034ab6 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +mutation { + databasesCreateTransaction( + ttl: 60 + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-graphql/examples/databases/decrement-document-attribute.md index 2e7970049d..e6032fd0e7 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/decrement-document-attribute.md @@ -5,7 +5,8 @@ mutation { documentId: "", attribute: "", value: 0, - min: 0 + min: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/delete-document.md b/docs/examples/1.8.x/client-graphql/examples/databases/delete-document.md index 848371bca0..2e172aa5dd 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/delete-document.md @@ -2,7 +2,8 @@ mutation { databasesDeleteDocument( databaseId: "", collectionId: "", - documentId: "" + documentId: "", + transactionId: "" ) { status } diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-graphql/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..cd29a0b8a6 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/databases/delete-transaction.md @@ -0,0 +1,7 @@ +mutation { + databasesDeleteTransaction( + transactionId: "" + ) { + status + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-graphql/examples/databases/get-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-graphql/examples/databases/increment-document-attribute.md index 322ed69ced..3518ff1583 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/increment-document-attribute.md @@ -5,7 +5,8 @@ mutation { documentId: "", attribute: "", value: 0, - max: 0 + max: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-graphql/examples/databases/list-transactions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/update-document.md b/docs/examples/1.8.x/client-graphql/examples/databases/update-document.md index aea605d9d7..cf43d9eed0 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/update-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-graphql/examples/databases/update-transaction.md new file mode 100644 index 0000000000..b56c7139ac --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/databases/update-transaction.md @@ -0,0 +1,14 @@ +mutation { + databasesUpdateTransaction( + transactionId: "", + commit: false, + rollback: false + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-graphql/examples/databases/upsert-document.md index 9d1e753081..d487c0d303 100644 --- a/docs/examples/1.8.x/client-graphql/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-graphql/examples/databases/upsert-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..bb2be8085a --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-operations.md @@ -0,0 +1,23 @@ +mutation { + tablesDBCreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-row.md index c7d2ec7d03..109bc008d6 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..0e874f0c78 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +mutation { + tablesDBCreateTransaction( + ttl: 60 + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/decrement-row-column.md index 398ec19901..1d57d79b54 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/decrement-row-column.md @@ -5,7 +5,8 @@ mutation { rowId: "", column: "", value: 0, - min: 0 + min: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-row.md index 1a08b0f60d..3b44913049 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-row.md @@ -2,7 +2,8 @@ mutation { tablesDBDeleteRow( databaseId: "", tableId: "", - rowId: "" + rowId: "", + transactionId: "" ) { status } diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..4a2d6f15a2 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/delete-transaction.md @@ -0,0 +1,7 @@ +mutation { + tablesDBDeleteTransaction( + transactionId: "" + ) { + status + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/increment-row-column.md index b7ff87f387..3ae008e718 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/increment-row-column.md @@ -5,7 +5,8 @@ mutation { rowId: "", column: "", value: 0, - max: 0 + max: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-row.md index 5a5b288ab8..aa89e6ae01 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..2094877303 --- /dev/null +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/update-transaction.md @@ -0,0 +1,14 @@ +mutation { + tablesDBUpdateTransaction( + transactionId: "", + commit: false, + rollback: false + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/client-graphql/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-graphql/examples/tablesdb/upsert-row.md index cc3b63de4a..3fe36ee7f1 100644 --- a/docs/examples/1.8.x/client-graphql/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-graphql/examples/tablesdb/upsert-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/create-document.md b/docs/examples/1.8.x/client-react-native/examples/databases/create-document.md index e7cffc13d4..3f7fd9af8f 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/create-document.md @@ -17,7 +17,8 @@ const result = await databases.createDocument({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/create-operations.md b/docs/examples/1.8.x/client-react-native/examples/databases/create-operations.md new file mode 100644 index 0000000000..bf02fd2c29 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/create-operations.md @@ -0,0 +1,24 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-react-native/examples/databases/create-transaction.md new file mode 100644 index 0000000000..07a2103e59 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-react-native/examples/databases/decrement-document-attribute.md index ddf43c9758..5edce7b091 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/decrement-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.decrementDocumentAttribute({ documentId: '', attribute: '', value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/delete-document.md b/docs/examples/1.8.x/client-react-native/examples/databases/delete-document.md index 828cefdda8..6cad3f9585 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/delete-document.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.deleteDocument({ databaseId: '', collectionId: '', - documentId: '' + documentId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-react-native/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..9ad2661362 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/get-document.md b/docs/examples/1.8.x/client-react-native/examples/databases/get-document.md index 7d28ee03d5..c61d396d3e 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/get-document.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/get-document.md @@ -10,7 +10,8 @@ const result = await databases.getDocument({ databaseId: '', collectionId: '', documentId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-react-native/examples/databases/get-transaction.md new file mode 100644 index 0000000000..47f93691e0 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-react-native/examples/databases/increment-document-attribute.md index c129c38a25..259a184e3a 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/increment-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.incrementDocumentAttribute({ documentId: '', attribute: '', value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/list-documents.md b/docs/examples/1.8.x/client-react-native/examples/databases/list-documents.md index 8d210b08e9..a744a531a1 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/list-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.listDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-react-native/examples/databases/list-transactions.md new file mode 100644 index 0000000000..2339673803 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/update-document.md b/docs/examples/1.8.x/client-react-native/examples/databases/update-document.md index ce4a6f2222..29674bd3d0 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/update-document.md @@ -11,7 +11,8 @@ const result = await databases.updateDocument({ collectionId: '', documentId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-react-native/examples/databases/update-transaction.md new file mode 100644 index 0000000000..c333850656 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/databases/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, Databases } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-react-native/examples/databases/upsert-document.md index a351ed7d4d..aa8fd1ca94 100644 --- a/docs/examples/1.8.x/client-react-native/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-react-native/examples/databases/upsert-document.md @@ -11,7 +11,8 @@ const result = await databases.upsertDocument({ collectionId: '', documentId: '', data: {}, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..1c76de77d2 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-row.md index a02a8376d5..6be799f547 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-row.md @@ -17,7 +17,8 @@ const result = await tablesDB.createRow({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..c2eca27695 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/decrement-row-column.md index e00eeb84bf..7bf6d77a46 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/decrement-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.decrementRowColumn({ rowId: '', column: '', value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-row.md index 624f1a191f..3ab81237eb 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-row.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.deleteRow({ databaseId: '', tableId: '', - rowId: '' + rowId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..121e6b3f67 --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-row.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-row.md index 081db46f18..a3a8775b4a 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-row.md @@ -10,7 +10,8 @@ const result = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..475e2d83ee --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/increment-row-column.md index d4b2cf98ad..4bda1efb24 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/increment-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.incrementRowColumn({ rowId: '', column: '', value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-rows.md index 6148e97e90..7cab86bc64 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.listRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..9d3004a90c --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-row.md index 01ed6e6acc..a83e3ea3e1 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.updateRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..de29a5bd2c --- /dev/null +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, TablesDB } from "react-native-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/client-react-native/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-react-native/examples/tablesdb/upsert-row.md index 72fad15ac4..7a82e0711e 100644 --- a/docs/examples/1.8.x/client-react-native/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-react-native/examples/tablesdb/upsert-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.upsertRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/client-rest/examples/databases/create-document.md b/docs/examples/1.8.x/client-rest/examples/databases/create-document.md index 12f2159402..c53babd482 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/create-document.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/create-document.md @@ -15,5 +15,6 @@ X-Appwrite-JWT: "age": 30, "isAdmin": false }, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/databases/create-operations.md b/docs/examples/1.8.x/client-rest/examples/databases/create-operations.md new file mode 100644 index 0000000000..602effd9a2 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/create-operations.md @@ -0,0 +1,21 @@ +POST /v1/databases/transactions/{transactionId}/operations HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "operations": [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] +} diff --git a/docs/examples/1.8.x/client-rest/examples/databases/create-transaction.md b/docs/examples/1.8.x/client-rest/examples/databases/create-transaction.md new file mode 100644 index 0000000000..c58528731e --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/create-transaction.md @@ -0,0 +1,11 @@ +POST /v1/databases/transactions HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "ttl": 60 +} diff --git a/docs/examples/1.8.x/client-rest/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/client-rest/examples/databases/decrement-document-attribute.md index 85ee70588b..be1d8d5bb6 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/decrement-document-attribute.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "value": 0, - "min": 0 + "min": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/databases/delete-document.md b/docs/examples/1.8.x/client-rest/examples/databases/delete-document.md index 2ee4f92ec4..3fa0a3ca21 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/delete-document.md @@ -6,3 +6,6 @@ X-Appwrite-Project: X-Appwrite-Session: X-Appwrite-JWT: +{ + "transactionId": "" +} diff --git a/docs/examples/1.8.x/client-rest/examples/databases/delete-transaction.md b/docs/examples/1.8.x/client-rest/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..1982dbb5a4 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/delete-transaction.md @@ -0,0 +1,8 @@ +DELETE /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + diff --git a/docs/examples/1.8.x/client-rest/examples/databases/get-transaction.md b/docs/examples/1.8.x/client-rest/examples/databases/get-transaction.md new file mode 100644 index 0000000000..09cc2e6c95 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/get-transaction.md @@ -0,0 +1,6 @@ +GET /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/client-rest/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/client-rest/examples/databases/increment-document-attribute.md index 68b091a31e..9eb873d6ff 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/increment-document-attribute.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "value": 0, - "max": 0 + "max": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/databases/list-transactions.md b/docs/examples/1.8.x/client-rest/examples/databases/list-transactions.md new file mode 100644 index 0000000000..f080df9228 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/list-transactions.md @@ -0,0 +1,6 @@ +GET /v1/databases/transactions HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/client-rest/examples/databases/update-document.md b/docs/examples/1.8.x/client-rest/examples/databases/update-document.md index ffc5d36011..f39cabfec3 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/update-document.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/update-document.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/databases/update-transaction.md b/docs/examples/1.8.x/client-rest/examples/databases/update-transaction.md new file mode 100644 index 0000000000..e8358a9051 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/databases/update-transaction.md @@ -0,0 +1,12 @@ +PATCH /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "commit": false, + "rollback": false +} diff --git a/docs/examples/1.8.x/client-rest/examples/databases/upsert-document.md b/docs/examples/1.8.x/client-rest/examples/databases/upsert-document.md index d2baeac6a8..c84d74a5ff 100644 --- a/docs/examples/1.8.x/client-rest/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-rest/examples/databases/upsert-document.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..dd3a05e271 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-operations.md @@ -0,0 +1,21 @@ +POST /v1/tablesdb/transactions/{transactionId}/operations HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "operations": [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] +} diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/create-row.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-row.md index 34d8ab5152..2abe0cc316 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-row.md @@ -15,5 +15,6 @@ X-Appwrite-JWT: "age": 30, "isAdmin": false }, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..e796ea2b57 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/create-transaction.md @@ -0,0 +1,11 @@ +POST /v1/tablesdb/transactions HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "ttl": 60 +} diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/decrement-row-column.md index 676e090150..b8dd25fdc5 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/decrement-row-column.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "value": 0, - "min": 0 + "min": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-row.md index f3ba056f7e..908bc4cc1f 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-row.md @@ -6,3 +6,6 @@ X-Appwrite-Project: X-Appwrite-Session: X-Appwrite-JWT: +{ + "transactionId": "" +} diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..54421d1c47 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/delete-transaction.md @@ -0,0 +1,8 @@ +DELETE /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..4276a3fb94 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/get-transaction.md @@ -0,0 +1,6 @@ +GET /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/increment-row-column.md index 5172cb420b..be9effd073 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/increment-row-column.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "value": 0, - "max": 0 + "max": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..91b6108463 --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/list-transactions.md @@ -0,0 +1,6 @@ +GET /v1/tablesdb/transactions HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/update-row.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/update-row.md index 40d276a0fe..7249d93906 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/update-row.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..f0f96d735f --- /dev/null +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/update-transaction.md @@ -0,0 +1,12 @@ +PATCH /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "commit": false, + "rollback": false +} diff --git a/docs/examples/1.8.x/client-rest/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-rest/examples/tablesdb/upsert-row.md index 581790fc01..93f6236eca 100644 --- a/docs/examples/1.8.x/client-rest/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-rest/examples/tablesdb/upsert-row.md @@ -8,5 +8,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/console-cli/examples/databases/create-operations.md b/docs/examples/1.8.x/console-cli/examples/databases/create-operations.md new file mode 100644 index 0000000000..367b435c9d --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/create-operations.md @@ -0,0 +1,2 @@ +appwrite databases create-operations \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/databases/create-transaction.md b/docs/examples/1.8.x/console-cli/examples/databases/create-transaction.md new file mode 100644 index 0000000000..ef348e75ad --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/create-transaction.md @@ -0,0 +1 @@ +appwrite databases create-transaction diff --git a/docs/examples/1.8.x/console-cli/examples/databases/delete-transaction.md b/docs/examples/1.8.x/console-cli/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..13c02b676b --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/delete-transaction.md @@ -0,0 +1,2 @@ +appwrite databases delete-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/databases/get-transaction.md b/docs/examples/1.8.x/console-cli/examples/databases/get-transaction.md new file mode 100644 index 0000000000..7fc80e40d2 --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/get-transaction.md @@ -0,0 +1,2 @@ +appwrite databases get-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/databases/list-transactions.md b/docs/examples/1.8.x/console-cli/examples/databases/list-transactions.md new file mode 100644 index 0000000000..f0cc259b43 --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/list-transactions.md @@ -0,0 +1 @@ +appwrite databases list-transactions diff --git a/docs/examples/1.8.x/console-cli/examples/databases/update-transaction.md b/docs/examples/1.8.x/console-cli/examples/databases/update-transaction.md new file mode 100644 index 0000000000..cda11d4e5f --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/databases/update-transaction.md @@ -0,0 +1,2 @@ +appwrite databases update-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/migrations/create-csv-migration.md b/docs/examples/1.8.x/console-cli/examples/migrations/create-csv-migration.md index 4b72b6f689..10e7b42b6c 100644 --- a/docs/examples/1.8.x/console-cli/examples/migrations/create-csv-migration.md +++ b/docs/examples/1.8.x/console-cli/examples/migrations/create-csv-migration.md @@ -1,4 +1,4 @@ appwrite migrations create-csv-migration \ --bucket-id \ --file-id \ - --resource-id [ID1:ID2] + --resource-id diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..dbea61862b --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/create-operations.md @@ -0,0 +1,2 @@ +appwrite tables-db create-operations \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..c6487fd74a --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/create-transaction.md @@ -0,0 +1 @@ +appwrite tables-db create-transaction diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..59e40d2fda --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/delete-transaction.md @@ -0,0 +1,2 @@ +appwrite tables-db delete-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..29ea9d75da --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/get-transaction.md @@ -0,0 +1,2 @@ +appwrite tables-db get-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..4dfbc2e567 --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/list-transactions.md @@ -0,0 +1 @@ +appwrite tables-db list-transactions diff --git a/docs/examples/1.8.x/console-cli/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/console-cli/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..6fa6d9510e --- /dev/null +++ b/docs/examples/1.8.x/console-cli/examples/tablesdb/update-transaction.md @@ -0,0 +1,2 @@ +appwrite tables-db update-transaction \ + --transaction-id diff --git a/docs/examples/1.8.x/console-web/examples/databases/create-document.md b/docs/examples/1.8.x/console-web/examples/databases/create-document.md index 40fbd4ad85..80f3fe66ad 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/create-document.md +++ b/docs/examples/1.8.x/console-web/examples/databases/create-document.md @@ -17,7 +17,8 @@ const result = await databases.createDocument({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/create-documents.md b/docs/examples/1.8.x/console-web/examples/databases/create-documents.md index 901133ac0a..10738b0f11 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/console-web/examples/databases/create-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.createDocuments({ databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/create-operations.md b/docs/examples/1.8.x/console-web/examples/databases/create-operations.md new file mode 100644 index 0000000000..ec511b0ddc --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/create-operations.md @@ -0,0 +1,24 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/create-transaction.md b/docs/examples/1.8.x/console-web/examples/databases/create-transaction.md new file mode 100644 index 0000000000..fc84f1d14f --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/console-web/examples/databases/decrement-document-attribute.md index ec014643bb..64f469c6ef 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/console-web/examples/databases/decrement-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.decrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/delete-document.md b/docs/examples/1.8.x/console-web/examples/databases/delete-document.md index 11cf317147..c2cdad3d84 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/console-web/examples/databases/delete-document.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.deleteDocument({ databaseId: '', collectionId: '', - documentId: '' + documentId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/delete-documents.md b/docs/examples/1.8.x/console-web/examples/databases/delete-documents.md index 45601a5985..0749194c97 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/console-web/examples/databases/delete-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.deleteDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/delete-transaction.md b/docs/examples/1.8.x/console-web/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..16b7c64022 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/get-document.md b/docs/examples/1.8.x/console-web/examples/databases/get-document.md index 00595b1033..8d893df7d9 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/get-document.md +++ b/docs/examples/1.8.x/console-web/examples/databases/get-document.md @@ -10,7 +10,8 @@ const result = await databases.getDocument({ databaseId: '', collectionId: '', documentId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/get-transaction.md b/docs/examples/1.8.x/console-web/examples/databases/get-transaction.md new file mode 100644 index 0000000000..8b6733b423 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/console-web/examples/databases/increment-document-attribute.md index 2207e94563..dbba4b0688 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/console-web/examples/databases/increment-document-attribute.md @@ -12,7 +12,8 @@ const result = await databases.incrementDocumentAttribute({ documentId: '', attribute: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/list-documents.md b/docs/examples/1.8.x/console-web/examples/databases/list-documents.md index dda036951c..f0a7a27890 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/console-web/examples/databases/list-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.listDocuments({ databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/list-transactions.md b/docs/examples/1.8.x/console-web/examples/databases/list-transactions.md new file mode 100644 index 0000000000..53e8d61f3e --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/update-document.md b/docs/examples/1.8.x/console-web/examples/databases/update-document.md index 168ad11bda..8a92d5b9f4 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/update-document.md +++ b/docs/examples/1.8.x/console-web/examples/databases/update-document.md @@ -11,7 +11,8 @@ const result = await databases.updateDocument({ collectionId: '', documentId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/update-documents.md b/docs/examples/1.8.x/console-web/examples/databases/update-documents.md index 7462502c81..9f9054bad2 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/console-web/examples/databases/update-documents.md @@ -10,7 +10,8 @@ const result = await databases.updateDocuments({ databaseId: '', collectionId: '', data: {}, // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/update-transaction.md b/docs/examples/1.8.x/console-web/examples/databases/update-transaction.md new file mode 100644 index 0000000000..4a219f4beb --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/databases/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, Databases } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const databases = new Databases(client); + +const result = await databases.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/upsert-document.md b/docs/examples/1.8.x/console-web/examples/databases/upsert-document.md index 3f8705f7c8..e7a52fd7cd 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/console-web/examples/databases/upsert-document.md @@ -11,7 +11,8 @@ const result = await databases.upsertDocument({ collectionId: '', documentId: '', data: {}, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/databases/upsert-documents.md b/docs/examples/1.8.x/console-web/examples/databases/upsert-documents.md index b045516281..cc561de247 100644 --- a/docs/examples/1.8.x/console-web/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/console-web/examples/databases/upsert-documents.md @@ -9,7 +9,8 @@ const databases = new Databases(client); const result = await databases.upsertDocuments({ databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/messaging/create-push.md b/docs/examples/1.8.x/console-web/examples/messaging/create-push.md index f0f54aa72f..783d8fe535 100644 --- a/docs/examples/1.8.x/console-web/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/console-web/examples/messaging/create-push.md @@ -15,7 +15,7 @@ const result = await messaging.createPush({ targets: [], // optional data: {}, // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/console-web/examples/messaging/update-push.md b/docs/examples/1.8.x/console-web/examples/messaging/update-push.md index 5f5d6e5da2..e1b0cc6b9d 100644 --- a/docs/examples/1.8.x/console-web/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/console-web/examples/messaging/update-push.md @@ -15,7 +15,7 @@ const result = await messaging.updatePush({ body: '', // optional data: {}, // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/console-web/examples/migrations/create-csv-migration.md b/docs/examples/1.8.x/console-web/examples/migrations/create-csv-migration.md index b25193ed21..f32adcb53c 100644 --- a/docs/examples/1.8.x/console-web/examples/migrations/create-csv-migration.md +++ b/docs/examples/1.8.x/console-web/examples/migrations/create-csv-migration.md @@ -9,7 +9,7 @@ const migrations = new Migrations(client); const result = await migrations.createCsvMigration({ bucketId: '', fileId: '', - resourceId: '[ID1:ID2]', + resourceId: '', internalFile: false // optional }); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/console-web/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..744627adae --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createOperations({ + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/create-row.md b/docs/examples/1.8.x/console-web/examples/tablesdb/create-row.md index 6123b5fc31..1991d44258 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/create-row.md @@ -17,7 +17,8 @@ const result = await tablesDB.createRow({ "age": 30, "isAdmin": false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/console-web/examples/tablesdb/create-rows.md index b827beb048..1054433a74 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/create-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.createRows({ databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/console-web/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..68465d4968 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.createTransaction({ + ttl: 60 // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/console-web/examples/tablesdb/decrement-row-column.md index 067afc7820..2f46b6d958 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/decrement-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.decrementRowColumn({ rowId: '', column: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-row.md index 9b7def4f00..1bcb477c18 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-row.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.deleteRow({ databaseId: '', tableId: '', - rowId: '' + rowId: '', + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-rows.md index 16868a0c6e..c955326753 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.deleteRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..b4f427a727 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.deleteTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/get-row.md b/docs/examples/1.8.x/console-web/examples/tablesdb/get-row.md index f170078430..831a9d1af2 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/get-row.md @@ -10,7 +10,8 @@ const result = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/console-web/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..99d405e0a2 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.getTransaction({ + transactionId: '' +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/console-web/examples/tablesdb/increment-row-column.md index 95694d49a4..a0047abb86 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/increment-row-column.md @@ -12,7 +12,8 @@ const result = await tablesDB.incrementRowColumn({ rowId: '', column: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/console-web/examples/tablesdb/list-rows.md index 46159b3d42..d87bd450d0 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/list-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.listRows({ databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/console-web/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..8ecfcefee1 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.listTransactions({ + queries: [] // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/update-row.md b/docs/examples/1.8.x/console-web/examples/tablesdb/update-row.md index 5d6109c04c..6193d79567 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/update-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.updateRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/console-web/examples/tablesdb/update-rows.md index 0ce117efa3..7601955b8b 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/update-rows.md @@ -10,7 +10,8 @@ const result = await tablesDB.updateRows({ databaseId: '', tableId: '', data: {}, // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/console-web/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..9095edc161 --- /dev/null +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +import { Client, TablesDB } from "@appwrite.io/console"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const tablesDB = new TablesDB(client); + +const result = await tablesDB.updateTransaction({ + transactionId: '', + commit: false, // optional + rollback: false // optional +}); + +console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-row.md index 665f181d8b..f56eff55fa 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-row.md @@ -11,7 +11,8 @@ const result = await tablesDB.upsertRow({ tableId: '', rowId: '', data: {}, // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-rows.md index 05e78e3efa..173d0e3065 100644 --- a/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/console-web/examples/tablesdb/upsert-rows.md @@ -9,7 +9,8 @@ const tablesDB = new TablesDB(client); const result = await tablesDB.upsertRows({ databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional }); console.log(result); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/create-document.md b/docs/examples/1.8.x/server-dart/examples/databases/create-document.md index e3bae98162..0f663a67f6 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/create-document.md @@ -19,4 +19,5 @@ Document result = await databases.createDocument( "isAdmin": false }, permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/create-documents.md b/docs/examples/1.8.x/server-dart/examples/databases/create-documents.md index ba0e34950b..6c77b78fe2 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/create-documents.md @@ -11,4 +11,5 @@ DocumentList result = await databases.createDocuments( databaseId: '', collectionId: '', documents: [], + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/create-operations.md b/docs/examples/1.8.x/server-dart/examples/databases/create-operations.md new file mode 100644 index 0000000000..2fe1121bf4 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/create-operations.md @@ -0,0 +1,23 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +Transaction result = await databases.createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ], // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-dart/examples/databases/create-transaction.md new file mode 100644 index 0000000000..69af666c73 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +Transaction result = await databases.createTransaction( + ttl: 60, // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-dart/examples/databases/decrement-document-attribute.md index e46244874d..6fb7ab68e6 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/decrement-document-attribute.md @@ -14,4 +14,5 @@ Document result = await databases.decrementDocumentAttribute( attribute: '', value: 0, // (optional) min: 0, // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/delete-document.md b/docs/examples/1.8.x/server-dart/examples/databases/delete-document.md index dd04d89959..eb9c3eba36 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/delete-document.md @@ -11,4 +11,5 @@ await databases.deleteDocument( databaseId: '', collectionId: '', documentId: '', + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-dart/examples/databases/delete-documents.md index 66bd5584c7..2e4e0c3cc2 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/delete-documents.md @@ -11,4 +11,5 @@ await databases.deleteDocuments( databaseId: '', collectionId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-dart/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..6cebc33f83 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/delete-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +await databases.deleteTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/get-document.md b/docs/examples/1.8.x/server-dart/examples/databases/get-document.md index 45745186e6..cd87138b7e 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/get-document.md @@ -12,4 +12,5 @@ Document result = await databases.getDocument( collectionId: '', documentId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-dart/examples/databases/get-transaction.md new file mode 100644 index 0000000000..dfa1b583c1 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/get-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +Transaction result = await databases.getTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-dart/examples/databases/increment-document-attribute.md index a2d2622629..ab108ebb27 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/increment-document-attribute.md @@ -14,4 +14,5 @@ Document result = await databases.incrementDocumentAttribute( attribute: '', value: 0, // (optional) max: 0, // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/list-documents.md b/docs/examples/1.8.x/server-dart/examples/databases/list-documents.md index cdecc59e33..74d849a2cb 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/list-documents.md @@ -11,4 +11,5 @@ DocumentList result = await databases.listDocuments( databaseId: '', collectionId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-dart/examples/databases/list-transactions.md new file mode 100644 index 0000000000..856e5e86ac --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/list-transactions.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +TransactionList result = await databases.listTransactions( + queries: [], // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/update-document.md b/docs/examples/1.8.x/server-dart/examples/databases/update-document.md index 47a1867c10..077a08f606 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/update-document.md @@ -13,4 +13,5 @@ Document result = await databases.updateDocument( documentId: '', data: {}, // (optional) permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/update-documents.md b/docs/examples/1.8.x/server-dart/examples/databases/update-documents.md index 70b7cbf86d..babc2d96fb 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/update-documents.md @@ -12,4 +12,5 @@ DocumentList result = await databases.updateDocuments( collectionId: '', data: {}, // (optional) queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-dart/examples/databases/update-transaction.md new file mode 100644 index 0000000000..b2b35f20eb --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/databases/update-transaction.md @@ -0,0 +1,14 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +Databases databases = Databases(client); + +Transaction result = await databases.updateTransaction( + transactionId: '', + commit: false, // (optional) + rollback: false, // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-dart/examples/databases/upsert-document.md index 93e306ebce..be4216da67 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/upsert-document.md @@ -13,4 +13,5 @@ Document result = await databases.upsertDocument( documentId: '', data: {}, permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-dart/examples/databases/upsert-documents.md index cd35014f63..009fe18b4a 100644 --- a/docs/examples/1.8.x/server-dart/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-dart/examples/databases/upsert-documents.md @@ -11,4 +11,5 @@ DocumentList result = await databases.upsertDocuments( databaseId: '', collectionId: '', documents: [], + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/messaging/create-push.md b/docs/examples/1.8.x/server-dart/examples/messaging/create-push.md index 58d82c7a0a..4b9f7d3c52 100644 --- a/docs/examples/1.8.x/server-dart/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-dart/examples/messaging/create-push.md @@ -16,7 +16,7 @@ Message result = await messaging.createPush( targets: [], // (optional) data: {}, // (optional) action: '', // (optional) - image: '[ID1:ID2]', // (optional) + image: '', // (optional) icon: '', // (optional) sound: '', // (optional) color: '', // (optional) diff --git a/docs/examples/1.8.x/server-dart/examples/messaging/update-push.md b/docs/examples/1.8.x/server-dart/examples/messaging/update-push.md index f7cc117b64..cbae5dfabb 100644 --- a/docs/examples/1.8.x/server-dart/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-dart/examples/messaging/update-push.md @@ -16,7 +16,7 @@ Message result = await messaging.updatePush( body: '', // (optional) data: {}, // (optional) action: '', // (optional) - image: '[ID1:ID2]', // (optional) + image: '', // (optional) icon: '', // (optional) sound: '', // (optional) color: '', // (optional) diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..2b5c046b14 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-operations.md @@ -0,0 +1,23 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ], // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-row.md index c2f5dd8293..255bd9615e 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-row.md @@ -19,4 +19,5 @@ Row result = await tablesDB.createRow( "isAdmin": false }, permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-rows.md index c22fdba506..6366006c31 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-rows.md @@ -11,4 +11,5 @@ RowList result = await tablesDB.createRows( databaseId: '', tableId: '', rows: [], + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..721ac4cf8b --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.createTransaction( + ttl: 60, // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/decrement-row-column.md index 8c376563d8..304e6b0219 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/decrement-row-column.md @@ -14,4 +14,5 @@ Row result = await tablesDB.decrementRowColumn( column: '', value: 0, // (optional) min: 0, // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-row.md index f3966c0930..da1e280cba 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-row.md @@ -11,4 +11,5 @@ await tablesDB.deleteRow( databaseId: '', tableId: '', rowId: '', + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-rows.md index eb2aebb3e6..6738ac78fc 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-rows.md @@ -11,4 +11,5 @@ await tablesDB.deleteRows( databaseId: '', tableId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..8dc80418a0 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/delete-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +await tablesDB.deleteTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/get-row.md index 0af17b612a..a0d7a83b39 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/get-row.md @@ -12,4 +12,5 @@ Row result = await tablesDB.getRow( tableId: '', rowId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..267af59aa9 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/get-transaction.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.getTransaction( + transactionId: '', +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/increment-row-column.md index bbe262e5c9..049a48a7d0 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/increment-row-column.md @@ -14,4 +14,5 @@ Row result = await tablesDB.incrementRowColumn( column: '', value: 0, // (optional) max: 0, // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/list-rows.md index 83bbe38fca..a429de72f6 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/list-rows.md @@ -11,4 +11,5 @@ RowList result = await tablesDB.listRows( databaseId: '', tableId: '', queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..d4b3a5e2c3 --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/list-transactions.md @@ -0,0 +1,12 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +TransactionList result = await tablesDB.listTransactions( + queries: [], // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-row.md index d66c24f505..e4f683cae0 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-row.md @@ -13,4 +13,5 @@ Row result = await tablesDB.updateRow( rowId: '', data: {}, // (optional) permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-rows.md index a0973b47d1..a7c0d61b45 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-rows.md @@ -12,4 +12,5 @@ RowList result = await tablesDB.updateRows( tableId: '', data: {}, // (optional) queries: [], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..bb3837d4ec --- /dev/null +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/update-transaction.md @@ -0,0 +1,14 @@ +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +TablesDB tablesDB = TablesDB(client); + +Transaction result = await tablesDB.updateTransaction( + transactionId: '', + commit: false, // (optional) + rollback: false, // (optional) +); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-row.md index fbe4d2928a..f9b8c848ae 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-row.md @@ -13,4 +13,5 @@ Row result = await tablesDB.upsertRow( rowId: '', data: {}, // (optional) permissions: ["read("any")"], // (optional) + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-rows.md index 031aa50bea..d48e9094f4 100644 --- a/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-dart/examples/tablesdb/upsert-rows.md @@ -11,4 +11,5 @@ RowList result = await tablesDB.upsertRows( databaseId: '', tableId: '', rows: [], + transactionId: '', // (optional) ); diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/create-document.md b/docs/examples/1.8.x/server-dotnet/examples/databases/create-document.md index 4a7444db7a..9fcfdd041d 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/create-document.md @@ -20,5 +20,6 @@ Document result = await databases.CreateDocument( age = 30, isAdmin = false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/create-documents.md b/docs/examples/1.8.x/server-dotnet/examples/databases/create-documents.md index dad710f0df..cc6cfb7606 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/create-documents.md @@ -12,5 +12,6 @@ Databases databases = new Databases(client); DocumentList result = await databases.CreateDocuments( databaseId: "", collectionId: "", - documents: new List() + documents: new List(), + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/create-operations.md b/docs/examples/1.8.x/server-dotnet/examples/databases/create-operations.md new file mode 100644 index 0000000000..701c6432b8 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/create-operations.md @@ -0,0 +1,25 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +Transaction result = await databases.CreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/databases/create-transaction.md new file mode 100644 index 0000000000..f8d7b34ffd --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/create-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +Transaction result = await databases.CreateTransaction( + ttl: 60 // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-dotnet/examples/databases/decrement-document-attribute.md index 2e48d4578e..9ff62a08aa 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/decrement-document-attribute.md @@ -15,5 +15,6 @@ Document result = await databases.DecrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/delete-document.md b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-document.md index 221b80e254..34bdbdafcd 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-document.md @@ -12,5 +12,6 @@ Databases databases = new Databases(client); await databases.DeleteDocument( databaseId: "", collectionId: "", - documentId: "" + documentId: "", + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-documents.md index a9bc9c277b..52f711b842 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-documents.md @@ -12,5 +12,6 @@ Databases databases = new Databases(client); await databases.DeleteDocuments( databaseId: "", collectionId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..713a75787e --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/delete-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +await databases.DeleteTransaction( + transactionId: "" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/get-document.md b/docs/examples/1.8.x/server-dotnet/examples/databases/get-document.md index d7e9b68807..bbafc3c888 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/get-document.md @@ -13,5 +13,6 @@ Document result = await databases.GetDocument( databaseId: "", collectionId: "", documentId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/databases/get-transaction.md new file mode 100644 index 0000000000..d66ab6e205 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/get-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +Transaction result = await databases.GetTransaction( + transactionId: "" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-dotnet/examples/databases/increment-document-attribute.md index 923c8d63e2..37a4ed76eb 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/increment-document-attribute.md @@ -15,5 +15,6 @@ Document result = await databases.IncrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/list-documents.md b/docs/examples/1.8.x/server-dotnet/examples/databases/list-documents.md index f59e4576bd..643c42015d 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/list-documents.md @@ -12,5 +12,6 @@ Databases databases = new Databases(client); DocumentList result = await databases.ListDocuments( databaseId: "", collectionId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-dotnet/examples/databases/list-transactions.md new file mode 100644 index 0000000000..3f2ce0df24 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/list-transactions.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +TransactionList result = await databases.ListTransactions( + queries: new List() // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/update-document.md b/docs/examples/1.8.x/server-dotnet/examples/databases/update-document.md index 3121c15e08..838b2790a9 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/update-document.md @@ -14,5 +14,6 @@ Document result = await databases.UpdateDocument( collectionId: "", documentId: "", data: [object], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/update-documents.md b/docs/examples/1.8.x/server-dotnet/examples/databases/update-documents.md index 63ded21ac9..077ec04da6 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/update-documents.md @@ -13,5 +13,6 @@ DocumentList result = await databases.UpdateDocuments( databaseId: "", collectionId: "", data: [object], // optional - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/databases/update-transaction.md new file mode 100644 index 0000000000..056f0d86f0 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/update-transaction.md @@ -0,0 +1,16 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +Databases databases = new Databases(client); + +Transaction result = await databases.UpdateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-document.md index c0876bfa73..e12c5dd0d7 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-document.md @@ -14,5 +14,6 @@ Document result = await databases.UpsertDocument( collectionId: "", documentId: "", data: [object], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-documents.md index 6c124c16e5..4fefbfcc38 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-dotnet/examples/databases/upsert-documents.md @@ -12,5 +12,6 @@ Databases databases = new Databases(client); DocumentList result = await databases.UpsertDocuments( databaseId: "", collectionId: "", - documents: new List() + documents: new List(), + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/messaging/create-push.md b/docs/examples/1.8.x/server-dotnet/examples/messaging/create-push.md index 1d2dbec1f2..ec90fa6d90 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-dotnet/examples/messaging/create-push.md @@ -19,7 +19,7 @@ Message result = await messaging.CreatePush( targets: new List(), // optional data: [object], // optional action: "", // optional - image: "[ID1:ID2]", // optional + image: "", // optional icon: "", // optional sound: "", // optional color: "", // optional diff --git a/docs/examples/1.8.x/server-dotnet/examples/messaging/update-push.md b/docs/examples/1.8.x/server-dotnet/examples/messaging/update-push.md index 37da215e82..b45752c815 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-dotnet/examples/messaging/update-push.md @@ -19,7 +19,7 @@ Message result = await messaging.UpdatePush( body: "", // optional data: [object], // optional action: "", // optional - image: "[ID1:ID2]", // optional + image: "", // optional icon: "", // optional sound: "", // optional color: "", // optional diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..0a8da3e8cc --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-operations.md @@ -0,0 +1,25 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +Transaction result = await tablesDB.CreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-row.md index 01a21b0dcd..8d56063f1f 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-row.md @@ -20,5 +20,6 @@ Row result = await tablesDB.CreateRow( age = 30, isAdmin = false }, - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-rows.md index c23e795a84..c73c474f45 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-rows.md @@ -12,5 +12,6 @@ TablesDB tablesDB = new TablesDB(client); RowList result = await tablesDB.CreateRows( databaseId: "", tableId: "", - rows: new List() + rows: new List(), + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..b42b087539 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/create-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +Transaction result = await tablesDB.CreateTransaction( + ttl: 60 // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/decrement-row-column.md index 66d98b65b9..19498d035f 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/decrement-row-column.md @@ -15,5 +15,6 @@ Row result = await tablesDB.DecrementRowColumn( rowId: "", column: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-row.md index 86d7fbf392..81bd084f62 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-row.md @@ -12,5 +12,6 @@ TablesDB tablesDB = new TablesDB(client); await tablesDB.DeleteRow( databaseId: "", tableId: "", - rowId: "" + rowId: "", + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-rows.md index 13d5758fdb..c0f656cda5 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-rows.md @@ -12,5 +12,6 @@ TablesDB tablesDB = new TablesDB(client); await tablesDB.DeleteRows( databaseId: "", tableId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..6e41c80c02 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/delete-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +await tablesDB.DeleteTransaction( + transactionId: "" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-row.md index 99d79ac3dd..66f6a230e3 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-row.md @@ -13,5 +13,6 @@ Row result = await tablesDB.GetRow( databaseId: "", tableId: "", rowId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..73e0904982 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/get-transaction.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +Transaction result = await tablesDB.GetTransaction( + transactionId: "" +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/increment-row-column.md index 48eabc34d4..cbac61f159 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/increment-row-column.md @@ -15,5 +15,6 @@ Row result = await tablesDB.IncrementRowColumn( rowId: "", column: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-rows.md index d3f860e869..79f809d35d 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-rows.md @@ -12,5 +12,6 @@ TablesDB tablesDB = new TablesDB(client); RowList result = await tablesDB.ListRows( databaseId: "", tableId: "", - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..b1a00e1a44 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/list-transactions.md @@ -0,0 +1,14 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +TransactionList result = await tablesDB.ListTransactions( + queries: new List() // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-row.md index 5eb5acdbd2..40f4eb4314 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-row.md @@ -14,5 +14,6 @@ Row result = await tablesDB.UpdateRow( tableId: "", rowId: "", data: [object], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-rows.md index 401464d66a..368977a0cc 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-rows.md @@ -13,5 +13,6 @@ RowList result = await tablesDB.UpdateRows( databaseId: "", tableId: "", data: [object], // optional - queries: new List() // optional + queries: new List(), // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..3b44fd5d37 --- /dev/null +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/update-transaction.md @@ -0,0 +1,16 @@ +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +Transaction result = await tablesDB.UpdateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-row.md index e1f68a7dfb..18b8419146 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-row.md @@ -14,5 +14,6 @@ Row result = await tablesDB.UpsertRow( tableId: "", rowId: "", data: [object], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-rows.md index 199dc2ba85..fde5df7149 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-dotnet/examples/tablesdb/upsert-rows.md @@ -12,5 +12,6 @@ TablesDB tablesDB = new TablesDB(client); RowList result = await tablesDB.UpsertRows( databaseId: "", tableId: "", - rows: new List() + rows: new List(), + transactionId: "" // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-go/examples/databases/create-document.md b/docs/examples/1.8.x/server-go/examples/databases/create-document.md index ea6305a06e..1c7a489129 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-go/examples/databases/create-document.md @@ -26,4 +26,5 @@ response, error := service.CreateDocument( "isAdmin": false }, databases.WithCreateDocumentPermissions(interface{}{"read("any")"}), + databases.WithCreateDocumentTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/create-documents.md b/docs/examples/1.8.x/server-go/examples/databases/create-documents.md index 8bd0a5761a..ae08b2e7d8 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-go/examples/databases/create-documents.md @@ -18,4 +18,5 @@ response, error := service.CreateDocuments( "", "", []interface{}{}, + databases.WithCreateDocumentsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/create-operations.md b/docs/examples/1.8.x/server-go/examples/databases/create-operations.md new file mode 100644 index 0000000000..c73ad57738 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/create-operations.md @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.CreateOperations( + "", + databases.WithCreateOperationsOperations(interface{}{ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + }), +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-go/examples/databases/create-transaction.md new file mode 100644 index 0000000000..f74b9152c1 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/create-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.CreateTransaction( + databases.WithCreateTransactionTtl(60), +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-go/examples/databases/decrement-document-attribute.md index af463743b5..7cb2c1c3c3 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-go/examples/databases/decrement-document-attribute.md @@ -21,4 +21,5 @@ response, error := service.DecrementDocumentAttribute( "", databases.WithDecrementDocumentAttributeValue(0), databases.WithDecrementDocumentAttributeMin(0), + databases.WithDecrementDocumentAttributeTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/delete-document.md b/docs/examples/1.8.x/server-go/examples/databases/delete-document.md index 6e9b58a56d..80e44b89eb 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-go/examples/databases/delete-document.md @@ -18,4 +18,5 @@ response, error := service.DeleteDocument( "", "", "", + databases.WithDeleteDocumentTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-go/examples/databases/delete-documents.md index 43e0d4cb36..5c070a0dc4 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-go/examples/databases/delete-documents.md @@ -18,4 +18,5 @@ response, error := service.DeleteDocuments( "", "", databases.WithDeleteDocumentsQueries([]interface{}{}), + databases.WithDeleteDocumentsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-go/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..b06f8bf6f3 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/delete-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.DeleteTransaction( + "", +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/get-document.md b/docs/examples/1.8.x/server-go/examples/databases/get-document.md index 5e63077aac..e075084f61 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-go/examples/databases/get-document.md @@ -19,4 +19,5 @@ response, error := service.GetDocument( "", "", databases.WithGetDocumentQueries([]interface{}{}), + databases.WithGetDocumentTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-go/examples/databases/get-transaction.md new file mode 100644 index 0000000000..4c15d73231 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/get-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.GetTransaction( + "", +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-go/examples/databases/increment-document-attribute.md index d2e91ee4c1..510d6486b8 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-go/examples/databases/increment-document-attribute.md @@ -21,4 +21,5 @@ response, error := service.IncrementDocumentAttribute( "", databases.WithIncrementDocumentAttributeValue(0), databases.WithIncrementDocumentAttributeMax(0), + databases.WithIncrementDocumentAttributeTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/list-documents.md b/docs/examples/1.8.x/server-go/examples/databases/list-documents.md index 0aaef36c59..3d83145a61 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-go/examples/databases/list-documents.md @@ -18,4 +18,5 @@ response, error := service.ListDocuments( "", "", databases.WithListDocumentsQueries([]interface{}{}), + databases.WithListDocumentsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-go/examples/databases/list-transactions.md new file mode 100644 index 0000000000..662874bb8c --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/list-transactions.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.ListTransactions( + databases.WithListTransactionsQueries([]interface{}{}), +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/update-document.md b/docs/examples/1.8.x/server-go/examples/databases/update-document.md index 90c0947536..314385d6a8 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-go/examples/databases/update-document.md @@ -20,4 +20,5 @@ response, error := service.UpdateDocument( "", databases.WithUpdateDocumentData(map[string]interface{}{}), databases.WithUpdateDocumentPermissions(interface{}{"read("any")"}), + databases.WithUpdateDocumentTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/update-documents.md b/docs/examples/1.8.x/server-go/examples/databases/update-documents.md index 7caee918e4..729656affd 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-go/examples/databases/update-documents.md @@ -19,4 +19,5 @@ response, error := service.UpdateDocuments( "", databases.WithUpdateDocumentsData(map[string]interface{}{}), databases.WithUpdateDocumentsQueries([]interface{}{}), + databases.WithUpdateDocumentsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-go/examples/databases/update-transaction.md new file mode 100644 index 0000000000..76ef4acb72 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/databases/update-transaction.md @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/databases" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := databases.New(client) + +response, error := service.UpdateTransaction( + "", + databases.WithUpdateTransactionCommit(false), + databases.WithUpdateTransactionRollback(false), +) diff --git a/docs/examples/1.8.x/server-go/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-go/examples/databases/upsert-document.md index 00cf8ad408..471c39185b 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-go/examples/databases/upsert-document.md @@ -20,4 +20,5 @@ response, error := service.UpsertDocument( "", map[string]interface{}{}, databases.WithUpsertDocumentPermissions(interface{}{"read("any")"}), + databases.WithUpsertDocumentTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-go/examples/databases/upsert-documents.md index a81ee4446e..9088883b1f 100644 --- a/docs/examples/1.8.x/server-go/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-go/examples/databases/upsert-documents.md @@ -18,4 +18,5 @@ response, error := service.UpsertDocuments( "", "", []interface{}{}, + databases.WithUpsertDocumentsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/messaging/create-push.md b/docs/examples/1.8.x/server-go/examples/messaging/create-push.md index fe2371bacd..a607f4391b 100644 --- a/docs/examples/1.8.x/server-go/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-go/examples/messaging/create-push.md @@ -23,7 +23,7 @@ response, error := service.CreatePush( messaging.WithCreatePushTargets([]interface{}{}), messaging.WithCreatePushData(map[string]interface{}{}), messaging.WithCreatePushAction(""), - messaging.WithCreatePushImage("[ID1:ID2]"), + messaging.WithCreatePushImage(""), messaging.WithCreatePushIcon(""), messaging.WithCreatePushSound(""), messaging.WithCreatePushColor(""), diff --git a/docs/examples/1.8.x/server-go/examples/messaging/update-push.md b/docs/examples/1.8.x/server-go/examples/messaging/update-push.md index 190627fa43..d364159e35 100644 --- a/docs/examples/1.8.x/server-go/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-go/examples/messaging/update-push.md @@ -23,7 +23,7 @@ response, error := service.UpdatePush( messaging.WithUpdatePushBody(""), messaging.WithUpdatePushData(map[string]interface{}{}), messaging.WithUpdatePushAction(""), - messaging.WithUpdatePushImage("[ID1:ID2]"), + messaging.WithUpdatePushImage(""), messaging.WithUpdatePushIcon(""), messaging.WithUpdatePushSound(""), messaging.WithUpdatePushColor(""), diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-go/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..330ece2bb9 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/create-operations.md @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.CreateOperations( + "", + tablesdb.WithCreateOperationsOperations(interface{}{ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + }), +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-go/examples/tablesdb/create-row.md index 596f11cf75..24054ace1d 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/create-row.md @@ -26,4 +26,5 @@ response, error := service.CreateRow( "isAdmin": false }, tablesdb.WithCreateRowPermissions(interface{}{"read("any")"}), + tablesdb.WithCreateRowTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-go/examples/tablesdb/create-rows.md index 2d83be06df..6ddeb067db 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/create-rows.md @@ -18,4 +18,5 @@ response, error := service.CreateRows( "", "", []interface{}{}, + tablesdb.WithCreateRowsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-go/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..165f897cf8 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/create-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.CreateTransaction( + tablesdb.WithCreateTransactionTtl(60), +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-go/examples/tablesdb/decrement-row-column.md index cd8d6c8983..a74bdda219 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/decrement-row-column.md @@ -21,4 +21,5 @@ response, error := service.DecrementRowColumn( "", tablesdb.WithDecrementRowColumnValue(0), tablesdb.WithDecrementRowColumnMin(0), + tablesdb.WithDecrementRowColumnTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-row.md index 8571a54c6c..39338452bc 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-row.md @@ -18,4 +18,5 @@ response, error := service.DeleteRow( "", "", "", + tablesdb.WithDeleteRowTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-rows.md index 364a4c942f..b9fa49b5fb 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-rows.md @@ -18,4 +18,5 @@ response, error := service.DeleteRows( "", "", tablesdb.WithDeleteRowsQueries([]interface{}{}), + tablesdb.WithDeleteRowsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..16ee050534 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/delete-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.DeleteTransaction( + "", +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-go/examples/tablesdb/get-row.md index 3a555f07c5..025c6b55a1 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/get-row.md @@ -19,4 +19,5 @@ response, error := service.GetRow( "", "", tablesdb.WithGetRowQueries([]interface{}{}), + tablesdb.WithGetRowTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-go/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..d478007b62 --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/get-transaction.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.GetTransaction( + "", +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-go/examples/tablesdb/increment-row-column.md index d072099cc6..4548f3cbb1 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/increment-row-column.md @@ -21,4 +21,5 @@ response, error := service.IncrementRowColumn( "", tablesdb.WithIncrementRowColumnValue(0), tablesdb.WithIncrementRowColumnMax(0), + tablesdb.WithIncrementRowColumnTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-go/examples/tablesdb/list-rows.md index 34a7b6fb98..784fbc81d9 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/list-rows.md @@ -18,4 +18,5 @@ response, error := service.ListRows( "", "", tablesdb.WithListRowsQueries([]interface{}{}), + tablesdb.WithListRowsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-go/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..7379d8555e --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/list-transactions.md @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.ListTransactions( + tablesdb.WithListTransactionsQueries([]interface{}{}), +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-go/examples/tablesdb/update-row.md index 1e819bb589..12ea0b10a4 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/update-row.md @@ -20,4 +20,5 @@ response, error := service.UpdateRow( "", tablesdb.WithUpdateRowData(map[string]interface{}{}), tablesdb.WithUpdateRowPermissions(interface{}{"read("any")"}), + tablesdb.WithUpdateRowTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-go/examples/tablesdb/update-rows.md index 3541dce134..ff6a81e57c 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/update-rows.md @@ -19,4 +19,5 @@ response, error := service.UpdateRows( "", tablesdb.WithUpdateRowsData(map[string]interface{}{}), tablesdb.WithUpdateRowsQueries([]interface{}{}), + tablesdb.WithUpdateRowsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-go/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..9842a4555c --- /dev/null +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/update-transaction.md @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1") + client.WithProject("") + client.WithKey("") +) + +service := tablesdb.New(client) + +response, error := service.UpdateTransaction( + "", + tablesdb.WithUpdateTransactionCommit(false), + tablesdb.WithUpdateTransactionRollback(false), +) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-row.md index 9fec778142..4caa5415e9 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-row.md @@ -20,4 +20,5 @@ response, error := service.UpsertRow( "", tablesdb.WithUpsertRowData(map[string]interface{}{}), tablesdb.WithUpsertRowPermissions(interface{}{"read("any")"}), + tablesdb.WithUpsertRowTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-rows.md index 5ded736cd0..a74d6ee9b1 100644 --- a/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-go/examples/tablesdb/upsert-rows.md @@ -18,4 +18,5 @@ response, error := service.UpsertRows( "", "", []interface{}{}, + tablesdb.WithUpsertRowsTransactionId(""), ) diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/create-document.md b/docs/examples/1.8.x/server-graphql/examples/databases/create-document.md index 39e4bba1cb..411615f7a7 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/create-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/create-documents.md b/docs/examples/1.8.x/server-graphql/examples/databases/create-documents.md index 8ce79dcbb5..01a8914d0e 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/create-documents.md @@ -2,7 +2,8 @@ mutation { databasesCreateDocuments( databaseId: "", collectionId: "", - documents: [] + documents: [], + transactionId: "" ) { total documents { diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/create-operations.md b/docs/examples/1.8.x/server-graphql/examples/databases/create-operations.md new file mode 100644 index 0000000000..1be3b39ee1 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/databases/create-operations.md @@ -0,0 +1,23 @@ +mutation { + databasesCreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-graphql/examples/databases/create-transaction.md new file mode 100644 index 0000000000..7fea034ab6 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +mutation { + databasesCreateTransaction( + ttl: 60 + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-graphql/examples/databases/decrement-document-attribute.md index 2e7970049d..e6032fd0e7 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/decrement-document-attribute.md @@ -5,7 +5,8 @@ mutation { documentId: "", attribute: "", value: 0, - min: 0 + min: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/delete-document.md b/docs/examples/1.8.x/server-graphql/examples/databases/delete-document.md index 848371bca0..2e172aa5dd 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/delete-document.md @@ -2,7 +2,8 @@ mutation { databasesDeleteDocument( databaseId: "", collectionId: "", - documentId: "" + documentId: "", + transactionId: "" ) { status } diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-graphql/examples/databases/delete-documents.md index 5822d3b950..aed5f6333f 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/delete-documents.md @@ -2,7 +2,8 @@ mutation { databasesDeleteDocuments( databaseId: "", collectionId: "", - queries: [] + queries: [], + transactionId: "" ) { total documents { diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-graphql/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..cd29a0b8a6 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/databases/delete-transaction.md @@ -0,0 +1,7 @@ +mutation { + databasesDeleteTransaction( + transactionId: "" + ) { + status + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-graphql/examples/databases/get-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-graphql/examples/databases/increment-document-attribute.md index 322ed69ced..3518ff1583 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/increment-document-attribute.md @@ -5,7 +5,8 @@ mutation { documentId: "", attribute: "", value: 0, - max: 0 + max: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-graphql/examples/databases/list-transactions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/update-document.md b/docs/examples/1.8.x/server-graphql/examples/databases/update-document.md index aea605d9d7..cf43d9eed0 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/update-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/update-documents.md b/docs/examples/1.8.x/server-graphql/examples/databases/update-documents.md index 83c0c07f84..d6eb18de2a 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/update-documents.md @@ -3,7 +3,8 @@ mutation { databaseId: "", collectionId: "", data: "{}", - queries: [] + queries: [], + transactionId: "" ) { total documents { diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-graphql/examples/databases/update-transaction.md new file mode 100644 index 0000000000..b56c7139ac --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/databases/update-transaction.md @@ -0,0 +1,14 @@ +mutation { + databasesUpdateTransaction( + transactionId: "", + commit: false, + rollback: false + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-graphql/examples/databases/upsert-document.md index 9d1e753081..d487c0d303 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/upsert-document.md @@ -4,7 +4,8 @@ mutation { collectionId: "", documentId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-graphql/examples/databases/upsert-documents.md index 2bfb765915..7852ba93f8 100644 --- a/docs/examples/1.8.x/server-graphql/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-graphql/examples/databases/upsert-documents.md @@ -2,7 +2,8 @@ mutation { databasesUpsertDocuments( databaseId: "", collectionId: "", - documents: [] + documents: [], + transactionId: "" ) { total documents { diff --git a/docs/examples/1.8.x/server-graphql/examples/messaging/create-push.md b/docs/examples/1.8.x/server-graphql/examples/messaging/create-push.md index 92264d1b67..dc924dfd32 100644 --- a/docs/examples/1.8.x/server-graphql/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-graphql/examples/messaging/create-push.md @@ -8,7 +8,7 @@ mutation { targets: [], data: "{}", action: "", - image: "[ID1:ID2]", + image: "", icon: "", sound: "", color: "", diff --git a/docs/examples/1.8.x/server-graphql/examples/messaging/update-push.md b/docs/examples/1.8.x/server-graphql/examples/messaging/update-push.md index 8ee2f57610..3436e0cf2f 100644 --- a/docs/examples/1.8.x/server-graphql/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-graphql/examples/messaging/update-push.md @@ -8,7 +8,7 @@ mutation { body: "", data: "{}", action: "", - image: "[ID1:ID2]", + image: "", icon: "", sound: "", color: "", diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..bb2be8085a --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-operations.md @@ -0,0 +1,23 @@ +mutation { + tablesDBCreateOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-row.md index c7d2ec7d03..109bc008d6 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-rows.md index 25dc9a367d..9364b5676d 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-rows.md @@ -2,7 +2,8 @@ mutation { tablesDBCreateRows( databaseId: "", tableId: "", - rows: [] + rows: [], + transactionId: "" ) { total rows { diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..0e874f0c78 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +mutation { + tablesDBCreateTransaction( + ttl: 60 + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/decrement-row-column.md index 398ec19901..1d57d79b54 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/decrement-row-column.md @@ -5,7 +5,8 @@ mutation { rowId: "", column: "", value: 0, - min: 0 + min: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-row.md index 1a08b0f60d..3b44913049 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-row.md @@ -2,7 +2,8 @@ mutation { tablesDBDeleteRow( databaseId: "", tableId: "", - rowId: "" + rowId: "", + transactionId: "" ) { status } diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-rows.md index dfa7c13779..9dae8fc679 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-rows.md @@ -2,7 +2,8 @@ mutation { tablesDBDeleteRows( databaseId: "", tableId: "", - queries: [] + queries: [], + transactionId: "" ) { total rows { diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..4a2d6f15a2 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/delete-transaction.md @@ -0,0 +1,7 @@ +mutation { + tablesDBDeleteTransaction( + transactionId: "" + ) { + status + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/increment-row-column.md index b7ff87f387..3ae008e718 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/increment-row-column.md @@ -5,7 +5,8 @@ mutation { rowId: "", column: "", value: 0, - max: 0 + max: 0, + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-row.md index 5a5b288ab8..aa89e6ae01 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-rows.md index 4816748352..5a6203a4dc 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-rows.md @@ -3,7 +3,8 @@ mutation { databaseId: "", tableId: "", data: "{}", - queries: [] + queries: [], + transactionId: "" ) { total rows { diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..2094877303 --- /dev/null +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/update-transaction.md @@ -0,0 +1,14 @@ +mutation { + tablesDBUpdateTransaction( + transactionId: "", + commit: false, + rollback: false + ) { + _id + _createdAt + _updatedAt + status + operations + expiresAt + } +} diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-row.md index cc3b63de4a..3fe36ee7f1 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-row.md @@ -4,7 +4,8 @@ mutation { tableId: "", rowId: "", data: "{}", - permissions: ["read("any")"] + permissions: ["read("any")"], + transactionId: "" ) { _id _sequence diff --git a/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-rows.md index f4e01c0af7..bbfc09c763 100644 --- a/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-graphql/examples/tablesdb/upsert-rows.md @@ -2,7 +2,8 @@ mutation { tablesDBUpsertRows( databaseId: "", tableId: "", - rows: [] + rows: [], + transactionId: "" ) { total rows { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md index d5e777d157..9c6357dfae 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md @@ -21,6 +21,7 @@ databases.createDocument( "isAdmin" to false ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md index 0de0c276ed..3a4540974b 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md @@ -13,6 +13,7 @@ databases.createDocuments( "", // databaseId "", // collectionId listOf(), // documents + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md new file mode 100644 index 0000000000..2dad8a15ac --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md @@ -0,0 +1,34 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.createOperations( + "", // transactionId + listOf( + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-transaction.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-transaction.md new file mode 100644 index 0000000000..5fb7c5936a --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/decrement-document-attribute.md index a44cc51260..a852083bce 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/decrement-document-attribute.md @@ -16,6 +16,7 @@ databases.decrementDocumentAttribute( "", // attribute 0, // value (optional) 0, // min (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/delete-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/delete-document.md index f6e6209f36..2f7003b234 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/delete-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/delete-document.md @@ -13,6 +13,7 @@ databases.deleteDocument( "", // databaseId "", // collectionId "", // documentId + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md index e8394b1ff9..958c40c382 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md @@ -13,6 +13,7 @@ databases.deleteDocuments( "", // databaseId "", // collectionId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/delete-transaction.md b/docs/examples/1.8.x/server-kotlin/java/databases/delete-transaction.md new file mode 100644 index 0000000000..200bbbdc26 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/delete-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md index 2719073a7d..489447f599 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md @@ -14,6 +14,7 @@ databases.getDocument( "", // collectionId "", // documentId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/get-transaction.md b/docs/examples/1.8.x/server-kotlin/java/databases/get-transaction.md new file mode 100644 index 0000000000..d896ca0751 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/get-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/increment-document-attribute.md index b5b5054e25..be837d00f8 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/increment-document-attribute.md @@ -16,6 +16,7 @@ databases.incrementDocumentAttribute( "", // attribute 0, // value (optional) 0, // max (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md index 36982c0eb0..1e84348c1a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md @@ -13,6 +13,7 @@ databases.listDocuments( "", // databaseId "", // collectionId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md new file mode 100644 index 0000000000..281fc1205b --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.listTransactions( + listOf(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md index f7b05c9601..f3019ab95b 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md @@ -15,6 +15,7 @@ databases.updateDocument( "", // documentId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md index b4138b41d2..a685ac81fc 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md @@ -14,6 +14,7 @@ databases.updateDocuments( "", // collectionId mapOf( "a" to "b" ), // data (optional) listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-transaction.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-transaction.md new file mode 100644 index 0000000000..8479ed31aa --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-transaction.md @@ -0,0 +1,25 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Databases; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +Databases databases = new Databases(client); + +databases.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md index daa44141e2..39864b9ac3 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md @@ -15,6 +15,7 @@ databases.upsertDocument( "", // documentId mapOf( "a" to "b" ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md index 95e9a33ef2..b8fcd8781a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md @@ -13,6 +13,7 @@ databases.upsertDocuments( "", // databaseId "", // collectionId listOf(), // documents + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md index 277ab9655c..14e8ca2679 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md @@ -18,7 +18,7 @@ messaging.createPush( listOf(), // targets (optional) mapOf( "a" to "b" ), // data (optional) "", // action (optional) - "[ID1:ID2]", // image (optional) + "", // image (optional) "", // icon (optional) "", // sound (optional) "", // color (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md index b7038de6a4..ce56683674 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md @@ -18,7 +18,7 @@ messaging.updatePush( "", // body (optional) mapOf( "a" to "b" ), // data (optional) "", // action (optional) - "[ID1:ID2]", // image (optional) + "", // image (optional) "", // icon (optional) "", // sound (optional) "", // color (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md new file mode 100644 index 0000000000..9504f623b3 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md @@ -0,0 +1,34 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.createOperations( + "", // transactionId + listOf( + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md index 6c7d84702d..d041511c11 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md @@ -21,6 +21,7 @@ tablesDB.createRow( "isAdmin" to false ), // data listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md index 21bdd21879..956d812165 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md @@ -13,6 +13,7 @@ tablesDB.createRows( "", // databaseId "", // tableId listOf(), // rows + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-transaction.md new file mode 100644 index 0000000000..3529956c54 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/decrement-row-column.md index b9f250f48f..78a811676d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/decrement-row-column.md @@ -16,6 +16,7 @@ tablesDB.decrementRowColumn( "", // column 0, // value (optional) 0, // min (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-row.md index fd66525a8d..5da1ba0cf3 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-row.md @@ -13,6 +13,7 @@ tablesDB.deleteRow( "", // databaseId "", // tableId "", // rowId + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md index 43b772e1a3..80ca0bb40f 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md @@ -13,6 +13,7 @@ tablesDB.deleteRows( "", // databaseId "", // tableId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..816b6e5dee --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md index 03cf3fb234..d642ebcaf1 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md @@ -14,6 +14,7 @@ tablesDB.getRow( "", // tableId "", // rowId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-transaction.md new file mode 100644 index 0000000000..dab07dce4e --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-transaction.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/increment-row-column.md index db48d05c37..33715721a8 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/increment-row-column.md @@ -16,6 +16,7 @@ tablesDB.incrementRowColumn( "", // column 0, // value (optional) 0, // max (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md index 52cf2a1670..96520e2bc5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md @@ -13,6 +13,7 @@ tablesDB.listRows( "", // databaseId "", // tableId listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md new file mode 100644 index 0000000000..acc4902da4 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md @@ -0,0 +1,23 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.listTransactions( + listOf(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md index bedc816f14..b4f9631222 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md @@ -15,6 +15,7 @@ tablesDB.updateRow( "", // rowId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md index 169b57c3e5..7d39e4422c 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md @@ -14,6 +14,7 @@ tablesDB.updateRows( "", // tableId mapOf( "a" to "b" ), // data (optional) listOf(), // queries (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-transaction.md new file mode 100644 index 0000000000..f043d5dc44 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-transaction.md @@ -0,0 +1,25 @@ +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md index d6155fcd1b..b6a986ef4d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md @@ -15,6 +15,7 @@ tablesDB.upsertRow( "", // rowId mapOf( "a" to "b" ), // data (optional) listOf("read("any")"), // permissions (optional) + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md index da15f6a0db..c4b2bf3857 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md @@ -13,6 +13,7 @@ tablesDB.upsertRows( "", // databaseId "", // tableId listOf(), // rows + "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-document.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-document.md index 1c1d628729..46cb711b62 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-document.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-document.md @@ -20,5 +20,6 @@ val response = databases.createDocument( "age" to 30, "isAdmin" to false ), - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-documents.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-documents.md index 41a98dc016..114d5cc707 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-documents.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-documents.md @@ -12,5 +12,6 @@ val databases = Databases(client) val response = databases.createDocuments( databaseId = "", collectionId = "", - documents = listOf() + documents = listOf(), + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md new file mode 100644 index 0000000000..1c741818b9 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md @@ -0,0 +1,25 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.createOperations( + transactionId = "", + operations = listOf( + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ) // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-transaction.md new file mode 100644 index 0000000000..83ff583038 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.createTransaction( + ttl = 60 // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/decrement-document-attribute.md index d0226c0bdb..3ccd662d59 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/decrement-document-attribute.md @@ -15,5 +15,6 @@ val response = databases.decrementDocumentAttribute( documentId = "", attribute = "", value = 0, // optional - min = 0 // optional + min = 0, // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-document.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-document.md index a9eea6b648..3be4372987 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-document.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-document.md @@ -12,5 +12,6 @@ val databases = Databases(client) val response = databases.deleteDocument( databaseId = "", collectionId = "", - documentId = "" + documentId = "", + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-documents.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-documents.md index c4caa63aae..9b9ea263c4 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-documents.md @@ -12,5 +12,6 @@ val databases = Databases(client) val response = databases.deleteDocuments( databaseId = "", collectionId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-transaction.md new file mode 100644 index 0000000000..ef11e9fad8 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/delete-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.deleteTransaction( + transactionId = "" +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-document.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-document.md index d21a19869b..98855d0984 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-document.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-document.md @@ -13,5 +13,6 @@ val response = databases.getDocument( databaseId = "", collectionId = "", documentId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-transaction.md new file mode 100644 index 0000000000..1e44376ab7 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/get-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.getTransaction( + transactionId = "" +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/increment-document-attribute.md index b56ed91d75..fb358868d2 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/increment-document-attribute.md @@ -15,5 +15,6 @@ val response = databases.incrementDocumentAttribute( documentId = "", attribute = "", value = 0, // optional - max = 0 // optional + max = 0, // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-documents.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-documents.md index ed9cb3165d..ab75493964 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-documents.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-documents.md @@ -12,5 +12,6 @@ val databases = Databases(client) val response = databases.listDocuments( databaseId = "", collectionId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-transactions.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-transactions.md new file mode 100644 index 0000000000..0d122b108d --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/list-transactions.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.listTransactions( + queries = listOf() // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-document.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-document.md index 4dd0349823..c64a705676 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-document.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-document.md @@ -14,5 +14,6 @@ val response = databases.updateDocument( collectionId = "", documentId = "", data = mapOf( "a" to "b" ), // optional - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-documents.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-documents.md index 9d6c2b5ea8..b5b76fcaee 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-documents.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-documents.md @@ -13,5 +13,6 @@ val response = databases.updateDocuments( databaseId = "", collectionId = "", data = mapOf( "a" to "b" ), // optional - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-transaction.md new file mode 100644 index 0000000000..834d0dc78d --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-transaction.md @@ -0,0 +1,16 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Databases + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val databases = Databases(client) + +val response = databases.updateTransaction( + transactionId = "", + commit = false, // optional + rollback = false // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-document.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-document.md index d8be0e13db..d6d6800864 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-document.md @@ -14,5 +14,6 @@ val response = databases.upsertDocument( collectionId = "", documentId = "", data = mapOf( "a" to "b" ), - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-documents.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-documents.md index ca861c61b2..db9e2b3e2d 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/upsert-documents.md @@ -12,5 +12,6 @@ val databases = Databases(client) val response = databases.upsertDocuments( databaseId = "", collectionId = "", - documents = listOf() + documents = listOf(), + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/messaging/create-push.md b/docs/examples/1.8.x/server-kotlin/kotlin/messaging/create-push.md index 5b07f5355b..d0d765a32a 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/messaging/create-push.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/messaging/create-push.md @@ -18,7 +18,7 @@ val response = messaging.createPush( targets = listOf(), // optional data = mapOf( "a" to "b" ), // optional action = "", // optional - image = "[ID1:ID2]", // optional + image = "", // optional icon = "", // optional sound = "", // optional color = "", // optional diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/messaging/update-push.md b/docs/examples/1.8.x/server-kotlin/kotlin/messaging/update-push.md index 710a37e518..0b9c4c6535 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/messaging/update-push.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/messaging/update-push.md @@ -18,7 +18,7 @@ val response = messaging.updatePush( body = "", // optional data = mapOf( "a" to "b" ), // optional action = "", // optional - image = "[ID1:ID2]", // optional + image = "", // optional icon = "", // optional sound = "", // optional color = "", // optional diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md new file mode 100644 index 0000000000..40c98d1c81 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md @@ -0,0 +1,25 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.createOperations( + transactionId = "", + operations = listOf( + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ) // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-row.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-row.md index 774800d8f4..b06038964b 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-row.md @@ -20,5 +20,6 @@ val response = tablesDB.createRow( "age" to 30, "isAdmin" to false ), - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-rows.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-rows.md index 1da47b5c18..8cef6028a2 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-rows.md @@ -12,5 +12,6 @@ val tablesDB = TablesDB(client) val response = tablesDB.createRows( databaseId = "", tableId = "", - rows = listOf() + rows = listOf(), + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-transaction.md new file mode 100644 index 0000000000..31385700c5 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.createTransaction( + ttl = 60 // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/decrement-row-column.md index e284ec3980..30a8d54b1a 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/decrement-row-column.md @@ -15,5 +15,6 @@ val response = tablesDB.decrementRowColumn( rowId = "", column = "", value = 0, // optional - min = 0 // optional + min = 0, // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-row.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-row.md index f24b9353e0..6ba7d6057c 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-row.md @@ -12,5 +12,6 @@ val tablesDB = TablesDB(client) val response = tablesDB.deleteRow( databaseId = "", tableId = "", - rowId = "" + rowId = "", + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-rows.md index c915a5c55a..da2b709f8a 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-rows.md @@ -12,5 +12,6 @@ val tablesDB = TablesDB(client) val response = tablesDB.deleteRows( databaseId = "", tableId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..2ef1f27a25 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/delete-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.deleteTransaction( + transactionId = "" +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-row.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-row.md index ec54631646..f92a1ccf27 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-row.md @@ -13,5 +13,6 @@ val response = tablesDB.getRow( databaseId = "", tableId = "", rowId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-transaction.md new file mode 100644 index 0000000000..f4467dc914 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/get-transaction.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.getTransaction( + transactionId = "" +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/increment-row-column.md index cac151e41b..af3676e75b 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/increment-row-column.md @@ -15,5 +15,6 @@ val response = tablesDB.incrementRowColumn( rowId = "", column = "", value = 0, // optional - max = 0 // optional + max = 0, // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-rows.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-rows.md index b0f5df476b..711e4e1a31 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-rows.md @@ -12,5 +12,6 @@ val tablesDB = TablesDB(client) val response = tablesDB.listRows( databaseId = "", tableId = "", - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-transactions.md new file mode 100644 index 0000000000..a060b9fac3 --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/list-transactions.md @@ -0,0 +1,14 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.listTransactions( + queries = listOf() // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-row.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-row.md index 9c5248f4e8..0fefb78e5f 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-row.md @@ -14,5 +14,6 @@ val response = tablesDB.updateRow( tableId = "", rowId = "", data = mapOf( "a" to "b" ), // optional - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-rows.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-rows.md index c285d5b4fb..61041a7783 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-rows.md @@ -13,5 +13,6 @@ val response = tablesDB.updateRows( databaseId = "", tableId = "", data = mapOf( "a" to "b" ), // optional - queries = listOf() // optional + queries = listOf(), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-transaction.md new file mode 100644 index 0000000000..a3797ca5ca --- /dev/null +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-transaction.md @@ -0,0 +1,16 @@ +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.TablesDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val tablesDB = TablesDB(client) + +val response = tablesDB.updateTransaction( + transactionId = "", + commit = false, // optional + rollback = false // optional +) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-row.md index 3fcbc61617..5bcc73b4b1 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-row.md @@ -14,5 +14,6 @@ val response = tablesDB.upsertRow( tableId = "", rowId = "", data = mapOf( "a" to "b" ), // optional - permissions = listOf("read("any")") // optional + permissions = listOf("read("any")"), // optional + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-rows.md index 7059c6018b..2f08375c4a 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/upsert-rows.md @@ -12,5 +12,6 @@ val tablesDB = TablesDB(client) val response = tablesDB.upsertRows( databaseId = "", tableId = "", - rows = listOf() + rows = listOf(), + transactionId = "" // optional ) diff --git a/docs/examples/1.8.x/server-php/examples/databases/create-document.md b/docs/examples/1.8.x/server-php/examples/databases/create-document.md index 9f2e8f3643..19d3cfb566 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-php/examples/databases/create-document.md @@ -21,5 +21,6 @@ $result = $databases->createDocument( 'age' => 30, 'isAdmin' => false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/create-documents.md b/docs/examples/1.8.x/server-php/examples/databases/create-documents.md index bc05f67260..ced7a5a83f 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-php/examples/databases/create-documents.md @@ -13,5 +13,6 @@ $databases = new Databases($client); $result = $databases->createDocuments( databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/create-operations.md b/docs/examples/1.8.x/server-php/examples/databases/create-operations.md new file mode 100644 index 0000000000..05038cb6f6 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/create-operations.md @@ -0,0 +1,26 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-php/examples/databases/create-transaction.md new file mode 100644 index 0000000000..ea6faf73b1 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/create-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->createTransaction( + ttl: 60 // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-php/examples/databases/decrement-document-attribute.md index 6464a26818..dfb1873cdd 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-php/examples/databases/decrement-document-attribute.md @@ -16,5 +16,6 @@ $result = $databases->decrementDocumentAttribute( documentId: '', attribute: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/delete-document.md b/docs/examples/1.8.x/server-php/examples/databases/delete-document.md index def7f24569..6e4d7aa2a6 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-php/examples/databases/delete-document.md @@ -13,5 +13,6 @@ $databases = new Databases($client); $result = $databases->deleteDocument( databaseId: '', collectionId: '', - documentId: '' + documentId: '', + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-php/examples/databases/delete-documents.md index 3552d85317..3b2b0c79c5 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-php/examples/databases/delete-documents.md @@ -13,5 +13,6 @@ $databases = new Databases($client); $result = $databases->deleteDocuments( databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-php/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..0559aace08 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/delete-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->deleteTransaction( + transactionId: '' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/get-document.md b/docs/examples/1.8.x/server-php/examples/databases/get-document.md index a3204c50a7..834602d89f 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-php/examples/databases/get-document.md @@ -14,5 +14,6 @@ $result = $databases->getDocument( databaseId: '', collectionId: '', documentId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-php/examples/databases/get-transaction.md new file mode 100644 index 0000000000..16ca28da1a --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/get-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->getTransaction( + transactionId: '' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-php/examples/databases/increment-document-attribute.md index 9ad4bdfdec..63162d3224 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-php/examples/databases/increment-document-attribute.md @@ -16,5 +16,6 @@ $result = $databases->incrementDocumentAttribute( documentId: '', attribute: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/list-documents.md b/docs/examples/1.8.x/server-php/examples/databases/list-documents.md index 07183ac8bf..10dcc82340 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-php/examples/databases/list-documents.md @@ -13,5 +13,6 @@ $databases = new Databases($client); $result = $databases->listDocuments( databaseId: '', collectionId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-php/examples/databases/list-transactions.md new file mode 100644 index 0000000000..858e905ba5 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/list-transactions.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->listTransactions( + queries: [] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/update-document.md b/docs/examples/1.8.x/server-php/examples/databases/update-document.md index f1c8a34680..d903252886 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-php/examples/databases/update-document.md @@ -15,5 +15,6 @@ $result = $databases->updateDocument( collectionId: '', documentId: '', data: [], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/update-documents.md b/docs/examples/1.8.x/server-php/examples/databases/update-documents.md index 51b4e18bc2..72632461a9 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-php/examples/databases/update-documents.md @@ -14,5 +14,6 @@ $result = $databases->updateDocuments( databaseId: '', collectionId: '', data: [], // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-php/examples/databases/update-transaction.md new file mode 100644 index 0000000000..750eb861f9 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/databases/update-transaction.md @@ -0,0 +1,17 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$databases = new Databases($client); + +$result = $databases->updateTransaction( + transactionId: '', + commit: false, // optional + rollback: false // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-php/examples/databases/upsert-document.md index 6cff8296a3..6db7462ac7 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-php/examples/databases/upsert-document.md @@ -15,5 +15,6 @@ $result = $databases->upsertDocument( collectionId: '', documentId: '', data: [], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-php/examples/databases/upsert-documents.md index d9f9efda5c..06d3a319af 100644 --- a/docs/examples/1.8.x/server-php/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-php/examples/databases/upsert-documents.md @@ -13,5 +13,6 @@ $databases = new Databases($client); $result = $databases->upsertDocuments( databaseId: '', collectionId: '', - documents: [] + documents: [], + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/messaging/create-push.md b/docs/examples/1.8.x/server-php/examples/messaging/create-push.md index 46aeeb3b8b..51fc0d0a92 100644 --- a/docs/examples/1.8.x/server-php/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-php/examples/messaging/create-push.md @@ -19,7 +19,7 @@ $result = $messaging->createPush( targets: [], // optional data: [], // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/server-php/examples/messaging/update-push.md b/docs/examples/1.8.x/server-php/examples/messaging/update-push.md index e1df0b9132..05a51783c9 100644 --- a/docs/examples/1.8.x/server-php/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-php/examples/messaging/update-push.md @@ -19,7 +19,7 @@ $result = $messaging->updatePush( body: '', // optional data: [], // optional action: '', // optional - image: '[ID1:ID2]', // optional + image: '', // optional icon: '', // optional sound: '', // optional color: '', // optional diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-php/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..429a0bb546 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/create-operations.md @@ -0,0 +1,26 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->createOperations( + transactionId: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-php/examples/tablesdb/create-row.md index fa5137b99e..873ecaf448 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/create-row.md @@ -21,5 +21,6 @@ $result = $tablesDB->createRow( 'age' => 30, 'isAdmin' => false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-php/examples/tablesdb/create-rows.md index 011443859f..44c9c7d140 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/create-rows.md @@ -13,5 +13,6 @@ $tablesDB = new TablesDB($client); $result = $tablesDB->createRows( databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-php/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..32488185b1 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/create-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->createTransaction( + ttl: 60 // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-php/examples/tablesdb/decrement-row-column.md index a58bd71071..ede258e8bd 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/decrement-row-column.md @@ -16,5 +16,6 @@ $result = $tablesDB->decrementRowColumn( rowId: '', column: '', value: null, // optional - min: null // optional + min: null, // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-row.md index 4ffc112d66..df87c5077b 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-row.md @@ -13,5 +13,6 @@ $tablesDB = new TablesDB($client); $result = $tablesDB->deleteRow( databaseId: '', tableId: '', - rowId: '' + rowId: '', + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-rows.md index 10a3c87ff2..79ed607c47 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-rows.md @@ -13,5 +13,6 @@ $tablesDB = new TablesDB($client); $result = $tablesDB->deleteRows( databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..d1650158c9 --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/delete-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->deleteTransaction( + transactionId: '' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-php/examples/tablesdb/get-row.md index 00ba9b65b5..4bbea5594d 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/get-row.md @@ -14,5 +14,6 @@ $result = $tablesDB->getRow( databaseId: '', tableId: '', rowId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-php/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..146e7d191b --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/get-transaction.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->getTransaction( + transactionId: '' +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-php/examples/tablesdb/increment-row-column.md index d72a1e374f..66bf2e8489 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/increment-row-column.md @@ -16,5 +16,6 @@ $result = $tablesDB->incrementRowColumn( rowId: '', column: '', value: null, // optional - max: null // optional + max: null, // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-php/examples/tablesdb/list-rows.md index c3b713703e..5f8c9aa1ef 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/list-rows.md @@ -13,5 +13,6 @@ $tablesDB = new TablesDB($client); $result = $tablesDB->listRows( databaseId: '', tableId: '', - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-php/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..15095d6f0c --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/list-transactions.md @@ -0,0 +1,15 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->listTransactions( + queries: [] // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-php/examples/tablesdb/update-row.md index 70e5d159fd..c01eba8d57 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/update-row.md @@ -15,5 +15,6 @@ $result = $tablesDB->updateRow( tableId: '', rowId: '', data: [], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-php/examples/tablesdb/update-rows.md index 8a676289d2..681a9f0d8b 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/update-rows.md @@ -14,5 +14,6 @@ $result = $tablesDB->updateRows( databaseId: '', tableId: '', data: [], // optional - queries: [] // optional + queries: [], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-php/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..fed3810b5a --- /dev/null +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/update-transaction.md @@ -0,0 +1,17 @@ +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->updateTransaction( + transactionId: '', + commit: false, // optional + rollback: false // optional +); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-row.md index 235f0e577b..bec3c0af92 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-row.md @@ -15,5 +15,6 @@ $result = $tablesDB->upsertRow( tableId: '', rowId: '', data: [], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-rows.md index c1890f1ea3..fb93df8bcd 100644 --- a/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-php/examples/tablesdb/upsert-rows.md @@ -13,5 +13,6 @@ $tablesDB = new TablesDB($client); $result = $tablesDB->upsertRows( databaseId: '', tableId: '', - rows: [] + rows: [], + transactionId: '' // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-python/examples/databases/create-document.md b/docs/examples/1.8.x/server-python/examples/databases/create-document.md index 3d7dee1a4f..f42a3d82b6 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-python/examples/databases/create-document.md @@ -19,5 +19,6 @@ result = databases.create_document( "age": 30, "isAdmin": False }, - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/create-documents.md b/docs/examples/1.8.x/server-python/examples/databases/create-documents.md index 1b94e5165a..97fa4c6840 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-python/examples/databases/create-documents.md @@ -11,5 +11,6 @@ databases = Databases(client) result = databases.create_documents( database_id = '', collection_id = '', - documents = [] + documents = [], + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/create-operations.md b/docs/examples/1.8.x/server-python/examples/databases/create-operations.md new file mode 100644 index 0000000000..d8fc1fa772 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/create-operations.md @@ -0,0 +1,24 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.create_operations( + transaction_id = '', + operations = [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-python/examples/databases/create-transaction.md new file mode 100644 index 0000000000..a733b658f8 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/create-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.create_transaction( + ttl = 60 # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-python/examples/databases/decrement-document-attribute.md index 3efedf766e..09ed9fcb47 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-python/examples/databases/decrement-document-attribute.md @@ -14,5 +14,6 @@ result = databases.decrement_document_attribute( document_id = '', attribute = '', value = None, # optional - min = None # optional + min = None, # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/delete-document.md b/docs/examples/1.8.x/server-python/examples/databases/delete-document.md index 57f8b3bd9d..89d85853e4 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-python/examples/databases/delete-document.md @@ -11,5 +11,6 @@ databases = Databases(client) result = databases.delete_document( database_id = '', collection_id = '', - document_id = '' + document_id = '', + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-python/examples/databases/delete-documents.md index a315f0c200..63130fb05e 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-python/examples/databases/delete-documents.md @@ -11,5 +11,6 @@ databases = Databases(client) result = databases.delete_documents( database_id = '', collection_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-python/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..fab1f2a586 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/delete-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.delete_transaction( + transaction_id = '' +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/get-document.md b/docs/examples/1.8.x/server-python/examples/databases/get-document.md index aff5008fa0..6cd0bc2b95 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-python/examples/databases/get-document.md @@ -12,5 +12,6 @@ result = databases.get_document( database_id = '', collection_id = '', document_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-python/examples/databases/get-transaction.md new file mode 100644 index 0000000000..2a89f3d83d --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/get-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.get_transaction( + transaction_id = '' +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-python/examples/databases/increment-document-attribute.md index 9ae1cedfe4..3e85656c10 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-python/examples/databases/increment-document-attribute.md @@ -14,5 +14,6 @@ result = databases.increment_document_attribute( document_id = '', attribute = '', value = None, # optional - max = None # optional + max = None, # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/list-documents.md b/docs/examples/1.8.x/server-python/examples/databases/list-documents.md index 8b450cd020..cecac30585 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-python/examples/databases/list-documents.md @@ -11,5 +11,6 @@ databases = Databases(client) result = databases.list_documents( database_id = '', collection_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-python/examples/databases/list-transactions.md new file mode 100644 index 0000000000..a410c96b05 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/list-transactions.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.list_transactions( + queries = [] # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/update-document.md b/docs/examples/1.8.x/server-python/examples/databases/update-document.md index 9ef6527934..c9ef02f72e 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-python/examples/databases/update-document.md @@ -13,5 +13,6 @@ result = databases.update_document( collection_id = '', document_id = '', data = {}, # optional - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/update-documents.md b/docs/examples/1.8.x/server-python/examples/databases/update-documents.md index 5a50d1a912..2aab8c61c4 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-python/examples/databases/update-documents.md @@ -12,5 +12,6 @@ result = databases.update_documents( database_id = '', collection_id = '', data = {}, # optional - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-python/examples/databases/update-transaction.md new file mode 100644 index 0000000000..571f98c7ce --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/databases/update-transaction.md @@ -0,0 +1,15 @@ +from appwrite.client import Client +from appwrite.services.databases import Databases + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +databases = Databases(client) + +result = databases.update_transaction( + transaction_id = '', + commit = False, # optional + rollback = False # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-python/examples/databases/upsert-document.md index c491ea4f44..e1a2f44d8c 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-python/examples/databases/upsert-document.md @@ -13,5 +13,6 @@ result = databases.upsert_document( collection_id = '', document_id = '', data = {}, - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-python/examples/databases/upsert-documents.md index 5136d5fcb1..f0720e34c0 100644 --- a/docs/examples/1.8.x/server-python/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-python/examples/databases/upsert-documents.md @@ -11,5 +11,6 @@ databases = Databases(client) result = databases.upsert_documents( database_id = '', collection_id = '', - documents = [] + documents = [], + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/messaging/create-push.md b/docs/examples/1.8.x/server-python/examples/messaging/create-push.md index 8671b56a39..b706234227 100644 --- a/docs/examples/1.8.x/server-python/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-python/examples/messaging/create-push.md @@ -17,7 +17,7 @@ result = messaging.create_push( targets = [], # optional data = {}, # optional action = '', # optional - image = '[ID1:ID2]', # optional + image = '', # optional icon = '', # optional sound = '', # optional color = '', # optional diff --git a/docs/examples/1.8.x/server-python/examples/messaging/update-push.md b/docs/examples/1.8.x/server-python/examples/messaging/update-push.md index e3bb02e71f..ce5d39466f 100644 --- a/docs/examples/1.8.x/server-python/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-python/examples/messaging/update-push.md @@ -17,7 +17,7 @@ result = messaging.update_push( body = '', # optional data = {}, # optional action = '', # optional - image = '[ID1:ID2]', # optional + image = '', # optional icon = '', # optional sound = '', # optional color = '', # optional diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-python/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..a4881a9e8f --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.create_operations( + transaction_id = '', + operations = [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-python/examples/tablesdb/create-row.md index d4c1cdad14..d2de58617f 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/create-row.md @@ -19,5 +19,6 @@ result = tables_db.create_row( "age": 30, "isAdmin": False }, - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-python/examples/tablesdb/create-rows.md index 656a47aa0b..1527e0b30d 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/create-rows.md @@ -11,5 +11,6 @@ tables_db = TablesDB(client) result = tables_db.create_rows( database_id = '', table_id = '', - rows = [] + rows = [], + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-python/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..05cc80eaa2 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.create_transaction( + ttl = 60 # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-python/examples/tablesdb/decrement-row-column.md index 096bc4dbaa..d207bb1b4d 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/decrement-row-column.md @@ -14,5 +14,6 @@ result = tables_db.decrement_row_column( row_id = '', column = '', value = None, # optional - min = None # optional + min = None, # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-row.md index 569b607020..3943ab27a5 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-row.md @@ -11,5 +11,6 @@ tables_db = TablesDB(client) result = tables_db.delete_row( database_id = '', table_id = '', - row_id = '' + row_id = '', + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-rows.md index c3e836e7c6..290d6d346b 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-rows.md @@ -11,5 +11,6 @@ tables_db = TablesDB(client) result = tables_db.delete_rows( database_id = '', table_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..6d2957f3d6 --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.delete_transaction( + transaction_id = '' +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-python/examples/tablesdb/get-row.md index c806214297..4398c9a43d 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/get-row.md @@ -12,5 +12,6 @@ result = tables_db.get_row( database_id = '', table_id = '', row_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-python/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..e50c63af9d --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.get_transaction( + transaction_id = '' +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-python/examples/tablesdb/increment-row-column.md index bcb88f7a31..8e121f65f6 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/increment-row-column.md @@ -14,5 +14,6 @@ result = tables_db.increment_row_column( row_id = '', column = '', value = None, # optional - max = None # optional + max = None, # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-python/examples/tablesdb/list-rows.md index 9ae7549fb0..eb0a4ed1b3 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/list-rows.md @@ -11,5 +11,6 @@ tables_db = TablesDB(client) result = tables_db.list_rows( database_id = '', table_id = '', - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-python/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..e597c2d6fd --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.list_transactions( + queries = [] # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-python/examples/tablesdb/update-row.md index 86d0cf2b8a..89dbfb0587 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/update-row.md @@ -13,5 +13,6 @@ result = tables_db.update_row( table_id = '', row_id = '', data = {}, # optional - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-python/examples/tablesdb/update-rows.md index 386ddf8b88..4717581276 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/update-rows.md @@ -12,5 +12,6 @@ result = tables_db.update_rows( database_id = '', table_id = '', data = {}, # optional - queries = [] # optional + queries = [], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-python/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..97b518dc6e --- /dev/null +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.update_transaction( + transaction_id = '', + commit = False, # optional + rollback = False # optional +) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-row.md index 068fded0c3..8539e12e96 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-row.md @@ -13,5 +13,6 @@ result = tables_db.upsert_row( table_id = '', row_id = '', data = {}, # optional - permissions = ["read("any")"] # optional + permissions = ["read("any")"], # optional + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-rows.md index 06436c0fa6..d42e259fb0 100644 --- a/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-python/examples/tablesdb/upsert-rows.md @@ -11,5 +11,6 @@ tables_db = TablesDB(client) result = tables_db.upsert_rows( database_id = '', table_id = '', - rows = [] + rows = [], + transaction_id = '' # optional ) diff --git a/docs/examples/1.8.x/server-rest/examples/databases/create-document.md b/docs/examples/1.8.x/server-rest/examples/databases/create-document.md index 57c38f0ba7..e1ca57e0bf 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/create-document.md @@ -16,5 +16,6 @@ X-Appwrite-JWT: "age": 30, "isAdmin": false }, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/create-documents.md b/docs/examples/1.8.x/server-rest/examples/databases/create-documents.md index cee5405fb2..4e23244620 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/create-documents.md @@ -8,5 +8,6 @@ X-Appwrite-Key: X-Appwrite-JWT: { - "documents": [] + "documents": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/create-operations.md b/docs/examples/1.8.x/server-rest/examples/databases/create-operations.md new file mode 100644 index 0000000000..212d60df29 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/create-operations.md @@ -0,0 +1,22 @@ +POST /v1/databases/transactions/{transactionId}/operations HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "operations": [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] +} diff --git a/docs/examples/1.8.x/server-rest/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-rest/examples/databases/create-transaction.md new file mode 100644 index 0000000000..3647e2d128 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/create-transaction.md @@ -0,0 +1,12 @@ +POST /v1/databases/transactions HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "ttl": 60 +} diff --git a/docs/examples/1.8.x/server-rest/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-rest/examples/databases/decrement-document-attribute.md index 78694a804d..0bd736b0e4 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/decrement-document-attribute.md @@ -9,5 +9,6 @@ X-Appwrite-Key: { "value": 0, - "min": 0 + "min": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/delete-document.md b/docs/examples/1.8.x/server-rest/examples/databases/delete-document.md index b5580b04bf..1031bfe18d 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/delete-document.md @@ -7,3 +7,6 @@ X-Appwrite-Session: X-Appwrite-Key: X-Appwrite-JWT: +{ + "transactionId": "" +} diff --git a/docs/examples/1.8.x/server-rest/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-rest/examples/databases/delete-documents.md index cb27719953..13ad4c6600 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/delete-documents.md @@ -6,5 +6,6 @@ X-Appwrite-Project: X-Appwrite-Key: { - "queries": [] + "queries": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-rest/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..09fc4e21f7 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/delete-transaction.md @@ -0,0 +1,9 @@ +DELETE /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + diff --git a/docs/examples/1.8.x/server-rest/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-rest/examples/databases/get-transaction.md new file mode 100644 index 0000000000..e3d89d72de --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/get-transaction.md @@ -0,0 +1,7 @@ +GET /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/server-rest/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-rest/examples/databases/increment-document-attribute.md index cd6b4122eb..924f0e9b0c 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/increment-document-attribute.md @@ -9,5 +9,6 @@ X-Appwrite-Key: { "value": 0, - "max": 0 + "max": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-rest/examples/databases/list-transactions.md new file mode 100644 index 0000000000..7a6f680a5f --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/list-transactions.md @@ -0,0 +1,7 @@ +GET /v1/databases/transactions HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/server-rest/examples/databases/update-document.md b/docs/examples/1.8.x/server-rest/examples/databases/update-document.md index 9a156375de..dafd249c31 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/update-document.md @@ -9,5 +9,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/update-documents.md b/docs/examples/1.8.x/server-rest/examples/databases/update-documents.md index 69ea7a0d6f..69d2dccd13 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/update-documents.md @@ -7,5 +7,6 @@ X-Appwrite-Key: { "data": {}, - "queries": [] + "queries": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-rest/examples/databases/update-transaction.md new file mode 100644 index 0000000000..21f1921e41 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/databases/update-transaction.md @@ -0,0 +1,13 @@ +PATCH /v1/databases/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "commit": false, + "rollback": false +} diff --git a/docs/examples/1.8.x/server-rest/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-rest/examples/databases/upsert-document.md index 97b61bfc7f..e4a9c7796a 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/upsert-document.md @@ -9,5 +9,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-rest/examples/databases/upsert-documents.md index 4bcb9cb0c0..7b15435e90 100644 --- a/docs/examples/1.8.x/server-rest/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-rest/examples/databases/upsert-documents.md @@ -6,5 +6,6 @@ X-Appwrite-Project: X-Appwrite-Key: { - "documents": [] + "documents": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/messaging/create-push.md b/docs/examples/1.8.x/server-rest/examples/messaging/create-push.md index a70702c014..f873bfe6ee 100644 --- a/docs/examples/1.8.x/server-rest/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-rest/examples/messaging/create-push.md @@ -14,7 +14,7 @@ X-Appwrite-Key: "targets": [], "data": {}, "action": "", - "image": "[ID1:ID2]", + "image": "", "icon": "", "sound": "", "color": "", diff --git a/docs/examples/1.8.x/server-rest/examples/messaging/update-push.md b/docs/examples/1.8.x/server-rest/examples/messaging/update-push.md index b3b953bc31..a3a6f84ae6 100644 --- a/docs/examples/1.8.x/server-rest/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-rest/examples/messaging/update-push.md @@ -13,7 +13,7 @@ X-Appwrite-Key: "body": "", "data": {}, "action": "", - "image": "[ID1:ID2]", + "image": "", "icon": "", "sound": "", "color": "", diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..87ca296db2 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-operations.md @@ -0,0 +1,22 @@ +POST /v1/tablesdb/transactions/{transactionId}/operations HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "operations": [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] +} diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-row.md index 3c42d0f172..cec287f4b3 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-row.md @@ -16,5 +16,6 @@ X-Appwrite-JWT: "age": 30, "isAdmin": false }, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-rows.md index 176b4cdb02..0ff4426b84 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-rows.md @@ -8,5 +8,6 @@ X-Appwrite-Key: X-Appwrite-JWT: { - "rows": [] + "rows": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..a2b8b184bd --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/create-transaction.md @@ -0,0 +1,12 @@ +POST /v1/tablesdb/transactions HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "ttl": 60 +} diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/decrement-row-column.md index 26d8e1118c..74b06974f1 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/decrement-row-column.md @@ -9,5 +9,6 @@ X-Appwrite-Key: { "value": 0, - "min": 0 + "min": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-row.md index 3dbbf45a3c..b1376ee7cd 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-row.md @@ -7,3 +7,6 @@ X-Appwrite-Session: X-Appwrite-Key: X-Appwrite-JWT: +{ + "transactionId": "" +} diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-rows.md index c57d62ede3..22eae7d599 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-rows.md @@ -6,5 +6,6 @@ X-Appwrite-Project: X-Appwrite-Key: { - "queries": [] + "queries": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..ed6e20dcc8 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/delete-transaction.md @@ -0,0 +1,9 @@ +DELETE /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..690351d711 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/get-transaction.md @@ -0,0 +1,7 @@ +GET /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/increment-row-column.md index d687727806..e9047669cd 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/increment-row-column.md @@ -9,5 +9,6 @@ X-Appwrite-Key: { "value": 0, - "max": 0 + "max": 0, + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..8b7f9301e3 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/list-transactions.md @@ -0,0 +1,7 @@ +GET /v1/tablesdb/transactions HTTP/1.1 +Host: cloud.appwrite.io +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-row.md index 51f10f7f97..5c37e3d929 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-row.md @@ -9,5 +9,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-rows.md index 2f282d8e13..c872907d30 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-rows.md @@ -7,5 +7,6 @@ X-Appwrite-Key: { "data": {}, - "queries": [] + "queries": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..118366e4a6 --- /dev/null +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/update-transaction.md @@ -0,0 +1,13 @@ +PATCH /v1/tablesdb/transactions/{transactionId} HTTP/1.1 +Host: cloud.appwrite.io +Content-Type: application/json +X-Appwrite-Response-Format: 1.8.0 +X-Appwrite-Project: +X-Appwrite-Key: +X-Appwrite-Session: +X-Appwrite-JWT: + +{ + "commit": false, + "rollback": false +} diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-row.md index edb74043fb..9f698fb195 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-row.md @@ -9,5 +9,6 @@ X-Appwrite-JWT: { "data": {}, - "permissions": ["read(\"any\")"] + "permissions": ["read(\"any\")"], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-rows.md index 147e4f66c3..822c7aaec2 100644 --- a/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-rest/examples/tablesdb/upsert-rows.md @@ -6,5 +6,6 @@ X-Appwrite-Project: X-Appwrite-Key: { - "rows": [] + "rows": [], + "transactionId": "" } diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/create-document.md b/docs/examples/1.8.x/server-ruby/examples/databases/create-document.md index 22ce5745fd..d12a3dbb8d 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/create-document.md @@ -20,5 +20,6 @@ result = databases.create_document( "age" => 30, "isAdmin" => false }, - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/create-documents.md b/docs/examples/1.8.x/server-ruby/examples/databases/create-documents.md index 16abc5e465..db45bd78a9 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/create-documents.md @@ -12,5 +12,6 @@ databases = Databases.new(client) result = databases.create_documents( database_id: '', collection_id: '', - documents: [] + documents: [], + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/create-operations.md b/docs/examples/1.8.x/server-ruby/examples/databases/create-operations.md new file mode 100644 index 0000000000..687932bd3e --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/create-operations.md @@ -0,0 +1,25 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.create_operations( + transaction_id: '', + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-ruby/examples/databases/create-transaction.md new file mode 100644 index 0000000000..83d2e4ea4d --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/create-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.create_transaction( + ttl: 60 # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-ruby/examples/databases/decrement-document-attribute.md index 9fd0191a0f..ecf15864da 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/decrement-document-attribute.md @@ -15,5 +15,6 @@ result = databases.decrement_document_attribute( document_id: '', attribute: '', value: null, # optional - min: null # optional + min: null, # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/delete-document.md b/docs/examples/1.8.x/server-ruby/examples/databases/delete-document.md index 2102d2695b..079247fc05 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/delete-document.md @@ -12,5 +12,6 @@ databases = Databases.new(client) result = databases.delete_document( database_id: '', collection_id: '', - document_id: '' + document_id: '', + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-ruby/examples/databases/delete-documents.md index d0f10d0b41..838660747c 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/delete-documents.md @@ -12,5 +12,6 @@ databases = Databases.new(client) result = databases.delete_documents( database_id: '', collection_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-ruby/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..2024818ad4 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/delete-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.delete_transaction( + transaction_id: '' +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/get-document.md b/docs/examples/1.8.x/server-ruby/examples/databases/get-document.md index f43a1a2924..47404fee80 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/get-document.md @@ -13,5 +13,6 @@ result = databases.get_document( database_id: '', collection_id: '', document_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-ruby/examples/databases/get-transaction.md new file mode 100644 index 0000000000..7d8349dc7d --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/get-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.get_transaction( + transaction_id: '' +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-ruby/examples/databases/increment-document-attribute.md index 3e8bfe0b2a..8f78675cdd 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/increment-document-attribute.md @@ -15,5 +15,6 @@ result = databases.increment_document_attribute( document_id: '', attribute: '', value: null, # optional - max: null # optional + max: null, # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/list-documents.md b/docs/examples/1.8.x/server-ruby/examples/databases/list-documents.md index 6617198d3f..666bfbd5ce 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/list-documents.md @@ -12,5 +12,6 @@ databases = Databases.new(client) result = databases.list_documents( database_id: '', collection_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-ruby/examples/databases/list-transactions.md new file mode 100644 index 0000000000..c041a05b5e --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/list-transactions.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.list_transactions( + queries: [] # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/update-document.md b/docs/examples/1.8.x/server-ruby/examples/databases/update-document.md index 485eb0485a..5831d68b5d 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/update-document.md @@ -14,5 +14,6 @@ result = databases.update_document( collection_id: '', document_id: '', data: {}, # optional - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/update-documents.md b/docs/examples/1.8.x/server-ruby/examples/databases/update-documents.md index 2f6907294f..c85f594e55 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/update-documents.md @@ -13,5 +13,6 @@ result = databases.update_documents( database_id: '', collection_id: '', data: {}, # optional - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-ruby/examples/databases/update-transaction.md new file mode 100644 index 0000000000..e53c148b18 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/databases/update-transaction.md @@ -0,0 +1,16 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +databases = Databases.new(client) + +result = databases.update_transaction( + transaction_id: '', + commit: false, # optional + rollback: false # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-ruby/examples/databases/upsert-document.md index 238081864f..e5daa554c4 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/upsert-document.md @@ -14,5 +14,6 @@ result = databases.upsert_document( collection_id: '', document_id: '', data: {}, - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-ruby/examples/databases/upsert-documents.md index 30c42aa439..b470b8d31f 100644 --- a/docs/examples/1.8.x/server-ruby/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-ruby/examples/databases/upsert-documents.md @@ -12,5 +12,6 @@ databases = Databases.new(client) result = databases.upsert_documents( database_id: '', collection_id: '', - documents: [] + documents: [], + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/messaging/create-push.md b/docs/examples/1.8.x/server-ruby/examples/messaging/create-push.md index 5c58fa542b..f4555aa967 100644 --- a/docs/examples/1.8.x/server-ruby/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-ruby/examples/messaging/create-push.md @@ -18,7 +18,7 @@ result = messaging.create_push( targets: [], # optional data: {}, # optional action: '', # optional - image: '[ID1:ID2]', # optional + image: '', # optional icon: '', # optional sound: '', # optional color: '', # optional diff --git a/docs/examples/1.8.x/server-ruby/examples/messaging/update-push.md b/docs/examples/1.8.x/server-ruby/examples/messaging/update-push.md index 42a5104ccb..19b273bb24 100644 --- a/docs/examples/1.8.x/server-ruby/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-ruby/examples/messaging/update-push.md @@ -18,7 +18,7 @@ result = messaging.update_push( body: '', # optional data: {}, # optional action: '', # optional - image: '[ID1:ID2]', # optional + image: '', # optional icon: '', # optional sound: '', # optional color: '', # optional diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..dfc7180990 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-operations.md @@ -0,0 +1,25 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.create_operations( + transaction_id: '', + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-row.md index 5e19136676..5622711642 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-row.md @@ -20,5 +20,6 @@ result = tables_db.create_row( "age" => 30, "isAdmin" => false }, - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-rows.md index f258d4d36f..76ee28699a 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-rows.md @@ -12,5 +12,6 @@ tables_db = TablesDB.new(client) result = tables_db.create_rows( database_id: '', table_id: '', - rows: [] + rows: [], + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..e3525afa19 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/create-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.create_transaction( + ttl: 60 # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/decrement-row-column.md index 21439740ed..62b01977b1 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/decrement-row-column.md @@ -15,5 +15,6 @@ result = tables_db.decrement_row_column( row_id: '', column: '', value: null, # optional - min: null # optional + min: null, # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-row.md index 704f52fc39..9747cb938a 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-row.md @@ -12,5 +12,6 @@ tables_db = TablesDB.new(client) result = tables_db.delete_row( database_id: '', table_id: '', - row_id: '' + row_id: '', + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-rows.md index 5b15c1748a..cf95cfb229 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-rows.md @@ -12,5 +12,6 @@ tables_db = TablesDB.new(client) result = tables_db.delete_rows( database_id: '', table_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..8fa7b3b8ac --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/delete-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.delete_transaction( + transaction_id: '' +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-row.md index 621c2e12f6..bdc1cf5fe6 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-row.md @@ -13,5 +13,6 @@ result = tables_db.get_row( database_id: '', table_id: '', row_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..ce8468ba3f --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/get-transaction.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.get_transaction( + transaction_id: '' +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/increment-row-column.md index bf9b6cc230..a20d2f5b36 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/increment-row-column.md @@ -15,5 +15,6 @@ result = tables_db.increment_row_column( row_id: '', column: '', value: null, # optional - max: null # optional + max: null, # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-rows.md index af971fbe10..b205cece5d 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-rows.md @@ -12,5 +12,6 @@ tables_db = TablesDB.new(client) result = tables_db.list_rows( database_id: '', table_id: '', - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..e969bc9965 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/list-transactions.md @@ -0,0 +1,14 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.list_transactions( + queries: [] # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-row.md index 7a48c5e3f6..02123051ca 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-row.md @@ -14,5 +14,6 @@ result = tables_db.update_row( table_id: '', row_id: '', data: {}, # optional - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-rows.md index 7316241139..7c538a137d 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-rows.md @@ -13,5 +13,6 @@ result = tables_db.update_rows( database_id: '', table_id: '', data: {}, # optional - queries: [] # optional + queries: [], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..2b8b3e7999 --- /dev/null +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/update-transaction.md @@ -0,0 +1,16 @@ +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +tables_db = TablesDB.new(client) + +result = tables_db.update_transaction( + transaction_id: '', + commit: false, # optional + rollback: false # optional +) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-row.md index 5eb4281002..9feb685927 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-row.md @@ -14,5 +14,6 @@ result = tables_db.upsert_row( table_id: '', row_id: '', data: {}, # optional - permissions: ["read("any")"] # optional + permissions: ["read("any")"], # optional + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-rows.md index c48211dcc0..e38f534ea9 100644 --- a/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-ruby/examples/tablesdb/upsert-rows.md @@ -12,5 +12,6 @@ tables_db = TablesDB.new(client) result = tables_db.upsert_rows( database_id: '', table_id: '', - rows: [] + rows: [], + transaction_id: '' # optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/create-document.md b/docs/examples/1.8.x/server-swift/examples/databases/create-document.md index cc25fd8df8..604bacdc2a 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/create-document.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/create-document.md @@ -18,6 +18,7 @@ let document = try await databases.createDocument( "age": 30, "isAdmin": false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/create-documents.md b/docs/examples/1.8.x/server-swift/examples/databases/create-documents.md index 2e992d9e3a..82a75125ae 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/create-documents.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/create-documents.md @@ -10,6 +10,7 @@ let databases = Databases(client) let documentList = try await databases.createDocuments( databaseId: "", collectionId: "", - documents: [] + documents: [], + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/create-operations.md b/docs/examples/1.8.x/server-swift/examples/databases/create-operations.md new file mode 100644 index 0000000000..7cab190bd3 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/create-operations.md @@ -0,0 +1,24 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let transaction = try await databases.createOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "collectionId": "", + "documentId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/create-transaction.md b/docs/examples/1.8.x/server-swift/examples/databases/create-transaction.md new file mode 100644 index 0000000000..333632c583 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/create-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let transaction = try await databases.createTransaction( + ttl: 60 // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/decrement-document-attribute.md b/docs/examples/1.8.x/server-swift/examples/databases/decrement-document-attribute.md index 81516fa26a..8c256ad208 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/decrement-document-attribute.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/decrement-document-attribute.md @@ -13,6 +13,7 @@ let document = try await databases.decrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/delete-document.md b/docs/examples/1.8.x/server-swift/examples/databases/delete-document.md index 1db59709ab..9120c3d0d0 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/delete-document.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/delete-document.md @@ -10,6 +10,7 @@ let databases = Databases(client) let result = try await databases.deleteDocument( databaseId: "", collectionId: "", - documentId: "" + documentId: "", + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/delete-documents.md b/docs/examples/1.8.x/server-swift/examples/databases/delete-documents.md index d5321f2b26..79ec772b3b 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/delete-documents.md @@ -10,6 +10,7 @@ let databases = Databases(client) let documentList = try await databases.deleteDocuments( databaseId: "", collectionId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/delete-transaction.md b/docs/examples/1.8.x/server-swift/examples/databases/delete-transaction.md new file mode 100644 index 0000000000..8ac62ef945 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/delete-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let result = try await databases.deleteTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/get-document.md b/docs/examples/1.8.x/server-swift/examples/databases/get-document.md index c92856a731..319a7ec3fb 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/get-document.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/get-document.md @@ -11,6 +11,7 @@ let document = try await databases.getDocument( databaseId: "", collectionId: "", documentId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/get-transaction.md b/docs/examples/1.8.x/server-swift/examples/databases/get-transaction.md new file mode 100644 index 0000000000..bfabd08b78 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/get-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let transaction = try await databases.getTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/increment-document-attribute.md b/docs/examples/1.8.x/server-swift/examples/databases/increment-document-attribute.md index 64ba46b413..7dd8805dc0 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/increment-document-attribute.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/increment-document-attribute.md @@ -13,6 +13,7 @@ let document = try await databases.incrementDocumentAttribute( documentId: "", attribute: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/list-documents.md b/docs/examples/1.8.x/server-swift/examples/databases/list-documents.md index 2cac9330b3..1147530d00 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/list-documents.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/list-documents.md @@ -10,6 +10,7 @@ let databases = Databases(client) let documentList = try await databases.listDocuments( databaseId: "", collectionId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/list-transactions.md b/docs/examples/1.8.x/server-swift/examples/databases/list-transactions.md new file mode 100644 index 0000000000..bb0d852d0a --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/list-transactions.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let transactionList = try await databases.listTransactions( + queries: [] // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/update-document.md b/docs/examples/1.8.x/server-swift/examples/databases/update-document.md index 7d452db284..b6260d754d 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/update-document.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/update-document.md @@ -12,6 +12,7 @@ let document = try await databases.updateDocument( collectionId: "", documentId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/update-documents.md b/docs/examples/1.8.x/server-swift/examples/databases/update-documents.md index 0e934b1424..f1fb34aa3c 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/update-documents.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/update-documents.md @@ -11,6 +11,7 @@ let documentList = try await databases.updateDocuments( databaseId: "", collectionId: "", data: [:], // optional - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/update-transaction.md b/docs/examples/1.8.x/server-swift/examples/databases/update-transaction.md new file mode 100644 index 0000000000..79f4939b5d --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/databases/update-transaction.md @@ -0,0 +1,15 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let databases = Databases(client) + +let transaction = try await databases.updateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/databases/upsert-document.md b/docs/examples/1.8.x/server-swift/examples/databases/upsert-document.md index e78bd458a0..26897f4ca5 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/upsert-document.md @@ -12,6 +12,7 @@ let document = try await databases.upsertDocument( collectionId: "", documentId: "", data: [:], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/databases/upsert-documents.md b/docs/examples/1.8.x/server-swift/examples/databases/upsert-documents.md index 544f02f9c0..92c5fd9810 100644 --- a/docs/examples/1.8.x/server-swift/examples/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-swift/examples/databases/upsert-documents.md @@ -10,6 +10,7 @@ let databases = Databases(client) let documentList = try await databases.upsertDocuments( databaseId: "", collectionId: "", - documents: [] + documents: [], + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/messaging/create-push.md b/docs/examples/1.8.x/server-swift/examples/messaging/create-push.md index 498eccb51a..ba03b3330d 100644 --- a/docs/examples/1.8.x/server-swift/examples/messaging/create-push.md +++ b/docs/examples/1.8.x/server-swift/examples/messaging/create-push.md @@ -17,7 +17,7 @@ let message = try await messaging.createPush( targets: [], // optional data: [:], // optional action: "", // optional - image: "[ID1:ID2]", // optional + image: "", // optional icon: "", // optional sound: "", // optional color: "", // optional diff --git a/docs/examples/1.8.x/server-swift/examples/messaging/update-push.md b/docs/examples/1.8.x/server-swift/examples/messaging/update-push.md index e443161aa9..b7b5bb0b38 100644 --- a/docs/examples/1.8.x/server-swift/examples/messaging/update-push.md +++ b/docs/examples/1.8.x/server-swift/examples/messaging/update-push.md @@ -17,7 +17,7 @@ let message = try await messaging.updatePush( body: "", // optional data: [:], // optional action: "", // optional - image: "[ID1:ID2]", // optional + image: "", // optional icon: "", // optional sound: "", // optional color: "", // optional diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-operations.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-operations.md new file mode 100644 index 0000000000..5ee356e55b --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-operations.md @@ -0,0 +1,24 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.createOperations( + transactionId: "", + operations: [ + { + "action": "create", + "databaseId": "", + "tableId": "", + "rowId": "", + "data": { + "name": "Walter O'Brien" + } + } + ] // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-row.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-row.md index 0c59a65755..049ef2da3d 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-row.md @@ -18,6 +18,7 @@ let row = try await tablesDB.createRow( "age": 30, "isAdmin": false ], - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-rows.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-rows.md index 25ea512590..63fafbd9e5 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-rows.md @@ -10,6 +10,7 @@ let tablesDB = TablesDB(client) let rowList = try await tablesDB.createRows( databaseId: "", tableId: "", - rows: [] + rows: [], + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/create-transaction.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-transaction.md new file mode 100644 index 0000000000..d826446ea2 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/create-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.createTransaction( + ttl: 60 // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/decrement-row-column.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/decrement-row-column.md index 196289e994..9c33055202 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/decrement-row-column.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/decrement-row-column.md @@ -13,6 +13,7 @@ let row = try await tablesDB.decrementRowColumn( rowId: "", column: "", value: 0, // optional - min: 0 // optional + min: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-row.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-row.md index 5449c74edb..a0a96eea4e 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-row.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-row.md @@ -10,6 +10,7 @@ let tablesDB = TablesDB(client) let result = try await tablesDB.deleteRow( databaseId: "", tableId: "", - rowId: "" + rowId: "", + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-rows.md index 85d8957f78..7235112eca 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-rows.md @@ -10,6 +10,7 @@ let tablesDB = TablesDB(client) let rowList = try await tablesDB.deleteRows( databaseId: "", tableId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-transaction.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-transaction.md new file mode 100644 index 0000000000..9a5d58bf42 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/delete-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let result = try await tablesDB.deleteTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/get-row.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/get-row.md index 17e6ccbb52..ecadab16aa 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/get-row.md @@ -11,6 +11,7 @@ let row = try await tablesDB.getRow( databaseId: "", tableId: "", rowId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/get-transaction.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/get-transaction.md new file mode 100644 index 0000000000..af0bd03b12 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/get-transaction.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.getTransaction( + transactionId: "" +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/increment-row-column.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/increment-row-column.md index 38aa7581c6..6c35ba8d4a 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/increment-row-column.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/increment-row-column.md @@ -13,6 +13,7 @@ let row = try await tablesDB.incrementRowColumn( rowId: "", column: "", value: 0, // optional - max: 0 // optional + max: 0, // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/list-rows.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/list-rows.md index 7320147685..92a2813f74 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/list-rows.md @@ -10,6 +10,7 @@ let tablesDB = TablesDB(client) let rowList = try await tablesDB.listRows( databaseId: "", tableId: "", - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/list-transactions.md new file mode 100644 index 0000000000..c6acb295d6 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/list-transactions.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let transactionList = try await tablesDB.listTransactions( + queries: [] // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/update-row.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-row.md index e06338b3d6..3ebd9e0970 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-row.md @@ -12,6 +12,7 @@ let row = try await tablesDB.updateRow( tableId: "", rowId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/update-rows.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-rows.md index 58894f5b85..f18a2a306c 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-rows.md @@ -11,6 +11,7 @@ let rowList = try await tablesDB.updateRows( databaseId: "", tableId: "", data: [:], // optional - queries: [] // optional + queries: [], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/update-transaction.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-transaction.md new file mode 100644 index 0000000000..faa7d07d63 --- /dev/null +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/update-transaction.md @@ -0,0 +1,15 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let tablesDB = TablesDB(client) + +let transaction = try await tablesDB.updateTransaction( + transactionId: "", + commit: false, // optional + rollback: false // optional +) + diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-row.md index dc133f6cd1..1b076266a3 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-row.md @@ -12,6 +12,7 @@ let row = try await tablesDB.upsertRow( tableId: "", rowId: "", data: [:], // optional - permissions: ["read("any")"] // optional + permissions: ["read("any")"], // optional + transactionId: "" // optional ) diff --git a/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-rows.md index fe35f0da91..027087b252 100644 --- a/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-swift/examples/tablesdb/upsert-rows.md @@ -10,6 +10,7 @@ let tablesDB = TablesDB(client) let rowList = try await tablesDB.upsertRows( databaseId: "", tableId: "", - rows: [] + rows: [], + transactionId: "" // optional ) diff --git a/docs/sdks/android/CHANGELOG.md b/docs/sdks/android/CHANGELOG.md index b6b9e4072b..559b61fbe9 100644 --- a/docs/sdks/android/CHANGELOG.md +++ b/docs/sdks/android/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 11.2.0 + +* Add transaction support for Databases and TablesDB + ## 11.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/apple/CHANGELOG.md b/docs/sdks/apple/CHANGELOG.md index dce2df1804..fed05027ce 100644 --- a/docs/sdks/apple/CHANGELOG.md +++ b/docs/sdks/apple/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.2.0 + +* Add transaction support for Databases and TablesDB + ## 13.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index 289c1ef0cb..202340eb76 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 10.2.0 + +* Add transaction support for Databases and TablesDB + ## 10.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index c25e66708b..3770468a34 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 19.2.0 + +* Add transaction support for Databases and TablesDB + ## 19.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/dotnet/CHANGELOG.md b/docs/sdks/dotnet/CHANGELOG.md index 7bd7d1d267..d2613ed142 100644 --- a/docs/sdks/dotnet/CHANGELOG.md +++ b/docs/sdks/dotnet/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 0.21.0 + +* Add transaction support for Databases and TablesDB + ## 0.20.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index 176efe4a5d..4fcd2778d8 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 20.2.0 + +* Add transaction support for Databases and TablesDB + ## 20.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/go/CHANGELOG.md b/docs/sdks/go/CHANGELOG.md index 9191f77aa3..88e81854f1 100644 --- a/docs/sdks/go/CHANGELOG.md +++ b/docs/sdks/go/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v0.13.0 + +* Add transaction support for Databases and TablesDB + ## v0.12.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/kotlin/CHANGELOG.md b/docs/sdks/kotlin/CHANGELOG.md index 40c32751d7..422b08c0ef 100644 --- a/docs/sdks/kotlin/CHANGELOG.md +++ b/docs/sdks/kotlin/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 12.2.0 + +* Add transaction support for Databases and TablesDB + ## 12.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/nodejs/CHANGELOG.md b/docs/sdks/nodejs/CHANGELOG.md index 7225d3d92f..a646e5694a 100644 --- a/docs/sdks/nodejs/CHANGELOG.md +++ b/docs/sdks/nodejs/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 20.2.0 + +* Add transaction support for Databases and TablesDB + ## 20.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/php/CHANGELOG.md b/docs/sdks/php/CHANGELOG.md index b1221264b2..d375d10c09 100644 --- a/docs/sdks/php/CHANGELOG.md +++ b/docs/sdks/php/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 17.4.0 + +* Add transaction support for Databases and TablesDB + ## 17.3.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/python/CHANGELOG.md b/docs/sdks/python/CHANGELOG.md index 584aa5cf5c..1c246243d3 100644 --- a/docs/sdks/python/CHANGELOG.md +++ b/docs/sdks/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.4.0 + +* Add transaction support for Databases and TablesDB + ## 13.3.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/react-native/CHANGELOG.md b/docs/sdks/react-native/CHANGELOG.md index 79ade8cc6c..ca732deec3 100644 --- a/docs/sdks/react-native/CHANGELOG.md +++ b/docs/sdks/react-native/CHANGELOG.md @@ -1,5 +1,9 @@ # Change log +## 0.17.0 + +* Add transaction support for Databases and TablesDB + ## 0.16.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/ruby/CHANGELOG.md b/docs/sdks/ruby/CHANGELOG.md index 0498d7cfb5..e39eedb436 100644 --- a/docs/sdks/ruby/CHANGELOG.md +++ b/docs/sdks/ruby/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 19.2.0 + +* Add transaction support for Databases and TablesDB + ## 19.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/swift/CHANGELOG.md b/docs/sdks/swift/CHANGELOG.md index 3d73a868f3..0d384b23f8 100644 --- a/docs/sdks/swift/CHANGELOG.md +++ b/docs/sdks/swift/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.2.0 + +* Add transaction support for Databases and TablesDB + ## 13.1.0 * Deprecate `createVerification` method in `Account` service diff --git a/docs/sdks/web/CHANGELOG.md b/docs/sdks/web/CHANGELOG.md index a28d868075..b88265dd01 100644 --- a/docs/sdks/web/CHANGELOG.md +++ b/docs/sdks/web/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 21.2.0 + +* Add transaction support for Databases and TablesDB + ## 21.1.0 * Deprecate `createVerification` method in `Account` service From 9cd284f5cd26c8fe63f3f4d501265d10f073d969 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Thu, 9 Oct 2025 14:51:49 +0530 Subject: [PATCH 306/385] Throw duplicate error when function id already exists --- app/config/errors.php | 5 ++ src/Appwrite/Extend/Exception.php | 1 + .../Functions/Http/Functions/Create.php | 65 ++++++++++--------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index f4439ff6ca..2e18f05797 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -578,6 +578,11 @@ return [ 'description' => 'The requested runtime is either inactive or unsupported. Please check the value of the _APP_FUNCTIONS_RUNTIMES environment variable.', 'code' => 404, ], + Exception::FUNCTION_ALREADY_EXISTS => [ + 'name' => Exception::FUNCTION_ALREADY_EXISTS, + 'description' => 'Function with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.', + 'code' => 409, + ], Exception::FUNCTION_ENTRYPOINT_MISSING => [ 'name' => Exception::FUNCTION_ENTRYPOINT_MISSING, 'description' => 'Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".', diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 61169685c4..6f8744568a 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -164,6 +164,7 @@ class Exception extends \Exception /** Functions */ public const string FUNCTION_NOT_FOUND = 'function_not_found'; + public const string FUNCTION_ALREADY_EXISTS = 'function_already_exists'; public const string FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; public const string FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; public const string FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout'; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index b00a2ad2bf..932f515fc1 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -23,6 +23,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -201,36 +202,40 @@ class Create extends Base throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".'); } - $function = $dbForProject->createDocument('functions', new Document([ - '$id' => $functionId, - 'execute' => $execute, - 'enabled' => $enabled, - 'live' => true, - 'logging' => $logging, - 'name' => $name, - 'runtime' => $runtime, - 'deploymentInternalId' => '', - 'deploymentId' => '', - 'events' => $events, - 'schedule' => $schedule, - 'scheduleInternalId' => '', - 'scheduleId' => '', - 'timeout' => $timeout, - 'entrypoint' => $entrypoint, - 'commands' => $commands, - 'scopes' => $scopes, - 'search' => implode(' ', [$functionId, $name, $runtime]), - 'version' => 'v5', - 'installationId' => $installation->getId(), - 'installationInternalId' => $installation->getSequence(), - 'providerRepositoryId' => $providerRepositoryId, - 'repositoryId' => '', - 'repositoryInternalId' => '', - 'providerBranch' => $providerBranch, - 'providerRootDirectory' => $providerRootDirectory, - 'providerSilentMode' => $providerSilentMode, - 'specification' => $specification - ])); + try { + $function = $dbForProject->createDocument('functions', new Document([ + '$id' => $functionId, + 'execute' => $execute, + 'enabled' => $enabled, + 'live' => true, + 'logging' => $logging, + 'name' => $name, + 'runtime' => $runtime, + 'deploymentInternalId' => '', + 'deploymentId' => '', + 'events' => $events, + 'schedule' => $schedule, + 'scheduleInternalId' => '', + 'scheduleId' => '', + 'timeout' => $timeout, + 'entrypoint' => $entrypoint, + 'commands' => $commands, + 'scopes' => $scopes, + 'search' => implode(' ', [$functionId, $name, $runtime]), + 'version' => 'v5', + 'installationId' => $installation->getId(), + 'installationInternalId' => $installation->getSequence(), + 'providerRepositoryId' => $providerRepositoryId, + 'repositoryId' => '', + 'repositoryInternalId' => '', + 'providerBranch' => $providerBranch, + 'providerRootDirectory' => $providerRootDirectory, + 'providerSilentMode' => $providerSilentMode, + 'specification' => $specification + ])); + } catch (DuplicateException) { + throw new Exception(Exception::FUNCTION_ALREADY_EXISTS); + } $schedule = Authorization::skip( fn () => $dbForPlatform->createDocument('schedules', new Document([ From a3103bf4d46d87fe16a5d6eaf7065e73e1149f94 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 23:37:28 +1300 Subject: [PATCH 307/385] Fix release --- app/config/platforms.php | 32 ++++++++++++++--------------- docs/sdks/android/CHANGELOG.md | 2 +- docs/sdks/apple/CHANGELOG.md | 2 +- docs/sdks/cli/CHANGELOG.md | 2 +- docs/sdks/dart/CHANGELOG.md | 2 +- docs/sdks/dotnet/CHANGELOG.md | 2 +- docs/sdks/flutter/CHANGELOG.md | 2 +- docs/sdks/go/CHANGELOG.md | 2 +- docs/sdks/kotlin/CHANGELOG.md | 2 +- docs/sdks/nodejs/CHANGELOG.md | 2 +- docs/sdks/php/CHANGELOG.md | 2 +- docs/sdks/python/CHANGELOG.md | 2 +- docs/sdks/react-native/CHANGELOG.md | 2 +- docs/sdks/ruby/CHANGELOG.md | 2 +- docs/sdks/swift/CHANGELOG.md | 2 +- docs/sdks/web/CHANGELOG.md | 2 +- 16 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 0f32c9f45c..22606d803c 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '21.2.0', + 'version' => '21.2.1', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -60,7 +60,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '20.2.0', + 'version' => '20.2.1', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '13.2.0', + 'version' => '13.2.1', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -116,7 +116,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '11.2.0', + 'version' => '11.2.1', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -139,7 +139,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.17.0', + 'version' => '0.17.1', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -207,7 +207,7 @@ return [ [ 'key' => 'web', 'name' => 'Console', - 'version' => '0.1.0', + 'version' => '0.1.1', 'url' => '', 'package' => '', 'enabled' => true, @@ -226,7 +226,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '10.2.0', + 'version' => '10.2.1', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, @@ -262,7 +262,7 @@ return [ [ 'key' => 'nodejs', 'name' => 'Node.js', - 'version' => '20.2.0', + 'version' => '20.2.1', 'url' => 'https://github.com/appwrite/sdk-for-node', 'package' => 'https://www.npmjs.com/package/node-appwrite', 'enabled' => true, @@ -281,7 +281,7 @@ return [ [ 'key' => 'php', 'name' => 'PHP', - 'version' => '17.4.0', + 'version' => '17.4.1', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -300,7 +300,7 @@ return [ [ 'key' => 'python', 'name' => 'Python', - 'version' => '13.4.0', + 'version' => '13.4.1', 'url' => 'https://github.com/appwrite/sdk-for-python', 'package' => 'https://pypi.org/project/appwrite/', 'enabled' => true, @@ -319,7 +319,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '19.2.0', + 'version' => '19.2.1', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, @@ -338,7 +338,7 @@ return [ [ 'key' => 'go', 'name' => 'Go', - 'version' => 'v0.13.0', + 'version' => 'v0.13.1', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => 'https://github.com/appwrite/sdk-for-go', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.21.0', + 'version' => '0.21.1', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, @@ -376,7 +376,7 @@ return [ [ 'key' => 'dart', 'name' => 'Dart', - 'version' => '19.2.0', + 'version' => '19.2.1', 'url' => 'https://github.com/appwrite/sdk-for-dart', 'package' => 'https://pub.dev/packages/dart_appwrite', 'enabled' => true, @@ -395,7 +395,7 @@ return [ [ 'key' => 'kotlin', 'name' => 'Kotlin', - 'version' => '12.2.0', + 'version' => '12.2.1', 'url' => 'https://github.com/appwrite/sdk-for-kotlin', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '13.2.0', + 'version' => '13.2.1', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/docs/sdks/android/CHANGELOG.md b/docs/sdks/android/CHANGELOG.md index 559b61fbe9..a05da45c4e 100644 --- a/docs/sdks/android/CHANGELOG.md +++ b/docs/sdks/android/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 11.2.0 +## 11.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/apple/CHANGELOG.md b/docs/sdks/apple/CHANGELOG.md index fed05027ce..6d67c4943f 100644 --- a/docs/sdks/apple/CHANGELOG.md +++ b/docs/sdks/apple/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 13.2.0 +## 13.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index 202340eb76..db3898dd00 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 10.2.0 +## 10.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index 3770468a34..c5ea578d20 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 19.2.0 +## 19.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/dotnet/CHANGELOG.md b/docs/sdks/dotnet/CHANGELOG.md index d2613ed142..deb467ce3d 100644 --- a/docs/sdks/dotnet/CHANGELOG.md +++ b/docs/sdks/dotnet/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 0.21.0 +## 0.21.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index 4fcd2778d8..7ac74d0c05 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 20.2.0 +## 20.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/go/CHANGELOG.md b/docs/sdks/go/CHANGELOG.md index 88e81854f1..243fdc14a1 100644 --- a/docs/sdks/go/CHANGELOG.md +++ b/docs/sdks/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## v0.13.0 +## v0.13.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/kotlin/CHANGELOG.md b/docs/sdks/kotlin/CHANGELOG.md index 422b08c0ef..fe8c8bf46a 100644 --- a/docs/sdks/kotlin/CHANGELOG.md +++ b/docs/sdks/kotlin/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 12.2.0 +## 12.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/nodejs/CHANGELOG.md b/docs/sdks/nodejs/CHANGELOG.md index a646e5694a..bd21b954f7 100644 --- a/docs/sdks/nodejs/CHANGELOG.md +++ b/docs/sdks/nodejs/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 20.2.0 +## 20.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/php/CHANGELOG.md b/docs/sdks/php/CHANGELOG.md index d375d10c09..3e5e810e84 100644 --- a/docs/sdks/php/CHANGELOG.md +++ b/docs/sdks/php/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 17.4.0 +## 17.4.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/python/CHANGELOG.md b/docs/sdks/python/CHANGELOG.md index 1c246243d3..7d8327b919 100644 --- a/docs/sdks/python/CHANGELOG.md +++ b/docs/sdks/python/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 13.4.0 +## 13.4.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/react-native/CHANGELOG.md b/docs/sdks/react-native/CHANGELOG.md index ca732deec3..f1f15907bc 100644 --- a/docs/sdks/react-native/CHANGELOG.md +++ b/docs/sdks/react-native/CHANGELOG.md @@ -1,6 +1,6 @@ # Change log -## 0.17.0 +## 0.17.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/ruby/CHANGELOG.md b/docs/sdks/ruby/CHANGELOG.md index e39eedb436..22f70c4c3a 100644 --- a/docs/sdks/ruby/CHANGELOG.md +++ b/docs/sdks/ruby/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 19.2.0 +## 19.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/swift/CHANGELOG.md b/docs/sdks/swift/CHANGELOG.md index 0d384b23f8..10119c524b 100644 --- a/docs/sdks/swift/CHANGELOG.md +++ b/docs/sdks/swift/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 13.2.0 +## 13.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/web/CHANGELOG.md b/docs/sdks/web/CHANGELOG.md index b88265dd01..338ec9095c 100644 --- a/docs/sdks/web/CHANGELOG.md +++ b/docs/sdks/web/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 21.2.0 +## 21.2.1 * Add transaction support for Databases and TablesDB From 32d6eaa21c078e703efe0b74f6416653511e6d5e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 23:37:44 +1300 Subject: [PATCH 308/385] Check expiry on stage --- .../Databases/Collections/Documents/Attribute/Decrement.php | 6 ++++++ .../Databases/Collections/Documents/Attribute/Increment.php | 6 ++++++ .../Http/Databases/Collections/Documents/Create.php | 6 ++++++ .../Http/Databases/Collections/Documents/Delete.php | 6 ++++++ .../Http/Databases/Collections/Documents/Update.php | 6 ++++++ .../Http/Databases/Collections/Documents/Upsert.php | 6 ++++++ .../Http/Databases/Transactions/Operations/Create.php | 6 ++++++ 7 files changed, 42 insertions(+) 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 cbe0ddceaf..da202b2f40 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 @@ -105,6 +105,12 @@ class Decrement extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); 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 22e19c69a5..543597612c 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 @@ -105,6 +105,12 @@ class Increment extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); 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 902a3585ba..41c50775a3 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 @@ -375,6 +375,12 @@ class Create extends Action throw new Exception(Exception::TRANSACTION_NOT_READY); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index be73068c06..200bd4db72 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -137,6 +137,12 @@ class Delete extends Action throw new Exception(Exception::TRANSACTION_NOT_READY); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); 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 a7d03de812..37adc1db70 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 @@ -252,6 +252,12 @@ class Update extends Action throw new Exception(Exception::TRANSACTION_NOT_READY); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 5d2f755212..92dd1c03b7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -261,6 +261,12 @@ class Upsert extends Action throw new Exception(Exception::TRANSACTION_NOT_READY); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + // Enforce max operations per transaction $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index c3ba45bdce..460da671b0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -77,6 +77,12 @@ class Create extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } + $now = new \DateTime(); + $expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now')); + if ($now > $expiresAt) { + throw new Exception(Exception::TRANSACTION_EXPIRED); + } + $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION; $existing = $transaction->getAttribute('operations', 0); From d82171b4dea01c374e8ddabac3ae318216799988 Mon Sep 17 00:00:00 2001 From: Veeresh <75656445+Veera-mulge@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:36:30 +0530 Subject: [PATCH 309/385] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cba193d4d..8018520fb7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> We just announced API for spatial columns for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-spatial-columns) +> We just announced Transactions API for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-transactions-api) > Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga) From bf589f74857d7d26d906c08ebc635cf4b7faa5c6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 02:16:58 +1300 Subject: [PATCH 310/385] Fix client side --- .../Documents/Attribute/Decrement.php | 8 +- .../Documents/Attribute/Increment.php | 8 +- .../Collections/Documents/Create.php | 4 +- .../Collections/Documents/Delete.php | 4 +- .../Collections/Documents/Update.php | 4 +- .../Collections/Documents/Upsert.php | 4 +- .../Http/Databases/Transactions/Create.php | 18 +- .../Transactions/Operations/Create.php | 106 +++-- .../Http/Databases/Transactions/Update.php | 8 +- .../Http/TablesDB/Transactions/Create.php | 1 + .../TablesDB/Transactions/PermissionsBase.php | 433 ++++++++++++++++++ 11 files changed, 542 insertions(+), 56 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 da202b2f40..316d0f1edb 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 @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Attribute; +use Appwrite\Auth\Auth; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -88,6 +89,9 @@ class Decrement extends Action public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -100,7 +104,9 @@ class Decrement extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } 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 543597612c..22fc1a8152 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 @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Attribute; +use Appwrite\Auth\Auth; use Appwrite\Event\Event; use Appwrite\Event\StatsUsage; use Appwrite\Extend\Exception; @@ -88,6 +89,9 @@ class Increment extends Action public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -100,7 +104,9 @@ class Increment extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } 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 41c50775a3..4254283432 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 @@ -367,7 +367,9 @@ class Create extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 200bd4db72..a4cef59e5f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -129,7 +129,9 @@ class Delete extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } 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 37adc1db70..a3a976c04a 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 @@ -244,7 +244,9 @@ class Update extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 92dd1c03b7..91a35d74c2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -253,7 +253,9 @@ class Upsert extends Action // Handle transaction staging if ($transactionId !== null) { - $transaction = $dbForProject->getDocument('transactions', $transactionId); + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php index 744ad33540..c4c5bf8b51 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php @@ -11,6 +11,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Helpers\Permission; use Utopia\Database\Validator\Authorization; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\Range; @@ -53,13 +54,28 @@ class Create extends Action ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) ->inject('response') ->inject('dbForProject') + ->inject('user') ->callback($this->action(...)); } - public function action(int $ttl, UtopiaResponse $response, Database $dbForProject): void + public function action(int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user): void { + $permissions = []; + if (!empty($user->getId())) { + $allowedPermissions = [ + Database::PERMISSION_READ, + Database::PERMISSION_UPDATE, + Database::PERMISSION_DELETE, + ]; + + foreach ($allowedPermissions as $permission) { + $permissions[] = (new Permission($permission, 'user', $user->getId()))->toString(); + } + } + $transaction = Authorization::skip(fn () => $dbForProject->createDocument('transactions', new Document([ '$id' => ID::unique(), + '$permissions' => $permissions, 'status' => 'pending', 'operations' => 0, 'expiresAt' => DateTime::addSeconds(new \DateTime(), $ttl), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 460da671b0..8aa132d15d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -72,8 +72,17 @@ class Create extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Operations array cannot be empty'); } - $transaction = Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)); - if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + // API keys and admins can read any transaction, regular users need permissions + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); + if ($transaction->isEmpty()) { + throw new Exception(Exception::TRANSACTION_NOT_FOUND); + } + if ($transaction->getAttribute('status', '') !== 'pending') { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction'); } @@ -93,9 +102,6 @@ class Create extends Action ); } - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $databases = $collections = $staged = $dependants = []; foreach ($operations as $operation) { if (!$isAPIKey && !$isPrivilegedUser && \in_array($operation['action'], [ @@ -146,54 +152,58 @@ class Create extends Action } } - $permissionType = match ($operation['action']) { - 'create', 'bulkCreate' => Database::PERMISSION_CREATE, - 'update', 'bulkUpdate', 'increment', 'decrement' => Database::PERMISSION_UPDATE, - 'delete', 'bulkDelete' => Database::PERMISSION_DELETE, - 'upsert', 'bulkUpsert' => ($document && !$document->isEmpty()) ? Database::PERMISSION_UPDATE : Database::PERMISSION_CREATE, - default => throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid action: ' . $operation['action']) - }; + // Bulk operations skip permission validation entirely (API key/admin only, already checked above) + if (!\in_array($operation['action'], ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) { + $permissionType = match ($operation['action']) { + 'create' => Database::PERMISSION_CREATE, + 'update', 'increment', 'decrement' => Database::PERMISSION_UPDATE, + 'delete' => Database::PERMISSION_DELETE, + 'upsert' => ($document && !$document->isEmpty()) ? Database::PERMISSION_UPDATE : Database::PERMISSION_CREATE, + default => throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid action: ' . $operation['action']) + }; - if (!$isAPIKey && !$isPrivilegedUser) { - $documentSecurity = $collection->getAttribute('documentSecurity', false); - $validator = new Authorization($permissionType); - $collectionValid = $validator->isValid($collection->getPermissionsByType($permissionType)); - $documentValid = false; - if ($document !== null && !$document->isEmpty() && $documentSecurity) { - if ($permissionType === Database::PERMISSION_UPDATE) { - $documentValid = $validator->isValid($document->getUpdate()); - } elseif ($permissionType === Database::PERMISSION_DELETE) { - $documentValid = $validator->isValid($document->getDelete()); + // For individual operations, enforce permissions unless using API key/admin + if (!$isAPIKey && !$isPrivilegedUser) { + $documentSecurity = $collection->getAttribute('documentSecurity', false); + $validator = new Authorization($permissionType); + $collectionValid = $validator->isValid($collection->getPermissionsByType($permissionType)); + $documentValid = false; + if ($document !== null && !$document->isEmpty() && $documentSecurity) { + if ($permissionType === Database::PERMISSION_UPDATE) { + $documentValid = $validator->isValid($document->getUpdate()); + } elseif ($permissionType === Database::PERMISSION_DELETE) { + $documentValid = $validator->isValid($document->getDelete()); + } } - } - if ($permissionType === Database::PERMISSION_CREATE || !$documentSecurity) { - if (!$collectionValid) { - throw new Exception(Exception::USER_UNAUTHORIZED); + if ($permissionType === Database::PERMISSION_CREATE || !$documentSecurity) { + if (!$collectionValid) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + } else { + if (!$collectionValid && !$documentValid) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } } - } else { - if (!$collectionValid && !$documentValid) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } - } - // Users can only set permissions for roles they have - if (isset($operation['data']['$permissions'])) { - $permissions = $operation['data']['$permissions']; - $roles = Authorization::getRoles(); - foreach (Database::PERMISSIONS as $type) { - foreach ($permissions as $permission) { - $permission = Permission::parse($permission); - if ($permission->getPermission() != $type) { - continue; - } - $role = (new Role( - $permission->getRole(), - $permission->getIdentifier(), - $permission->getDimension() - ))->toString(); - if (!Authorization::isRole($role)) { - throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); + // Users can only set permissions for roles they have + if (isset($operation['data']['$permissions'])) { + $permissions = $operation['data']['$permissions']; + $roles = Authorization::getRoles(); + foreach (Database::PERMISSIONS as $type) { + foreach ($permissions as $permission) { + $permission = Permission::parse($permission); + if ($permission->getPermission() != $type) { + continue; + } + $role = (new Role( + $permission->getRole(), + $permission->getIdentifier(), + $permission->getDimension() + ))->toString(); + if (!Authorization::isRole($role)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); + } } } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 5d29bba34b..927adb9bc7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Transactions; +use Appwrite\Auth\Auth; use Appwrite\Databases\TransactionState; use Appwrite\Event\Delete; use Appwrite\Event\Event; @@ -110,7 +111,12 @@ class Update extends Action throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot commit and rollback at the same time'); } - $transaction = Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + $transaction = ($isAPIKey || $isPrivilegedUser) + ? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId)) + : $dbForProject->getDocument('transactions', $transactionId); if ($transaction->isEmpty()) { throw new Exception(Exception::TRANSACTION_NOT_FOUND); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php index e6c24b3341..a375d47c3c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php @@ -49,6 +49,7 @@ class Create extends TransactionsCreate ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) ->inject('response') ->inject('dbForProject') + ->inject('user') ->callback($this->action(...)); } } diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php index 9dac59e2ec..a1082256ee 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsBase.php @@ -780,4 +780,437 @@ trait PermissionsBase $this->assertEquals(200, $rollback['headers']['status-code']); } + + /** + * Test that one user cannot read another user's transaction + */ + public function testUserCannotReadAnotherUsersTransaction(): void + { + // Create user 1 (fresh) and their transaction + $user1 = $this->getUser(true); + $user1Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user1['session'], + ]; + + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction1['headers']['status-code']); + $transactionId1 = $transaction1['body']['$id']; + + // Create user 2 (fresh) + $user2 = $this->getUser(true); // Fresh user + $user2Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user2['session'], + ]; + + // User 2 tries to read User 1's transaction - should fail + $readAttempt = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers)); + + // This should fail with 404 Not Found (transaction doesn't exist for this user) + $this->assertEquals(404, $readAttempt['headers']['status-code']); + + // Verify User 1 can still read their own transaction + $readOwn = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(200, $readOwn['headers']['status-code']); + $this->assertEquals($transactionId1, $readOwn['body']['$id']); + } + + /** + * Test that one user cannot list another user's transactions + */ + public function testUserCannotListAnotherUsersTransactions(): void + { + // Create user 1 (fresh) with transactions + $user1 = $this->getUser(true); + $user1Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user1['session'], + ]; + + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction1['headers']['status-code']); + + $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction2['headers']['status-code']); + + // Create user 2 (fresh) with their own transaction + $user2 = $this->getUser(true); // Fresh user + $user2Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user2['session'], + ]; + + $transaction3 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers)); + + $this->assertEquals(201, $transaction3['headers']['status-code']); + + // User 2 lists transactions - should only see their own + $listUser2 = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers)); + + $this->assertEquals(200, $listUser2['headers']['status-code']); + $this->assertEquals(1, $listUser2['body']['total']); + $this->assertEquals($transaction3['body']['$id'], $listUser2['body']['transactions'][0]['$id']); + + // User 1 lists transactions - should only see their own (2 transactions) + $listUser1 = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(200, $listUser1['headers']['status-code']); + $this->assertEquals(2, $listUser1['body']['total']); + + // Verify neither of user1's transactions appear in user2's list + $user2TransactionIds = array_column($listUser2['body']['transactions'], '$id'); + $this->assertNotContains($transaction1['body']['$id'], $user2TransactionIds); + $this->assertNotContains($transaction2['body']['$id'], $user2TransactionIds); + } + + /** + * Test that one user cannot update another user's transaction + */ + public function testUserCannotUpdateAnotherUsersTransaction(): void + { + // Create user 1 (fresh) and their transaction + $user1 = $this->getUser(true); + $user1Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user1['session'], + ]; + + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction1['headers']['status-code']); + $transactionId1 = $transaction1['body']['$id']; + + // Create user 2 (fresh) + $user2 = $this->getUser(true); // Fresh user + $user2Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user2['session'], + ]; + + // User 2 tries to commit User 1's transaction - should fail + $commitAttempt = $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers), [ + 'commit' => true, + ]); + + // This should fail with 404 Not Found + $this->assertEquals(404, $commitAttempt['headers']['status-code']); + + // User 2 tries to rollback User 1's transaction - should also fail + $rollbackAttempt = $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers), [ + 'rollback' => true, + ]); + + // This should also fail with 404 Not Found + $this->assertEquals(404, $rollbackAttempt['headers']['status-code']); + + // Verify User 1 can still commit their own transaction + $commitOwn = $this->client->call(Client::METHOD_PATCH, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers), [ + 'commit' => true, + ]); + + $this->assertEquals(200, $commitOwn['headers']['status-code']); + } + + /** + * Test that one user cannot delete another user's transaction + */ + public function testUserCannotDeleteAnotherUsersTransaction(): void + { + // Create user 1 (fresh) and their transaction + $user1 = $this->getUser(true); + $user1Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user1['session'], + ]; + + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction1['headers']['status-code']); + $transactionId1 = $transaction1['body']['$id']; + + // Create user 2 (fresh) + $user2 = $this->getUser(true); // Fresh user + $user2Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user2['session'], + ]; + + // User 2 tries to delete User 1's transaction - should fail + $deleteAttempt = $this->client->call(Client::METHOD_DELETE, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers)); + + // This should fail with 404 Not Found + $this->assertEquals(404, $deleteAttempt['headers']['status-code']); + + // Verify User 1 can still access their transaction + $readOwn = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(200, $readOwn['headers']['status-code']); + + // User 1 can delete their own transaction + $deleteOwn = $this->client->call(Client::METHOD_DELETE, '/tablesdb/transactions/' . $transactionId1, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(204, $deleteOwn['headers']['status-code']); + } + + /** + * Test that one user cannot add operations to another user's transaction + */ + public function testUserCannotAddOperationsToAnotherUsersTransaction(): void + { + // Create a collection for testing + $collection = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => 'permTest11', + 'name' => 'Permission Test 11', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'rowSecurity' => false, + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $this->permissionsDatabase . '/tables/' . $collection['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + sleep(2); + + // Create user 1 (fresh) and their transaction + $user1 = $this->getUser(true); + $user1Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user1['session'], + ]; + + $transaction1 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers)); + + $this->assertEquals(201, $transaction1['headers']['status-code']); + $transactionId1 = $transaction1['body']['$id']; + + // Create user 2 (fresh) + $user2 = $this->getUser(true); // Fresh user + $user2Headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user2['session'], + ]; + + // User 2 tries to add operations to User 1's transaction - should fail + $operationAttempt = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId1 . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user2Headers), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'maliciousDoc', + 'data' => ['title' => 'Malicious Document'], + ]] + ]); + + // This should fail with 404 Not Found + $this->assertEquals(404, $operationAttempt['headers']['status-code']); + + // Verify User 1 can still add operations to their own transaction + $operationOwn = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions/' . $transactionId1 . '/operations', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $user1Headers), [ + 'operations' => [[ + 'action' => 'create', + 'databaseId' => $this->permissionsDatabase, + 'tableId' => $collection['body']['$id'], + 'rowId' => 'legitimateDoc', + 'data' => ['title' => 'Legitimate Document'], + ]] + ]); + + $this->assertEquals(201, $operationOwn['headers']['status-code']); + $this->assertEquals(1, $operationOwn['body']['operations']); + } + + /** + * Test that an authenticated user can successfully list their own transactions + */ + public function testAuthenticatedUserCanListTheirOwnTransactions(): void + { + // Create an authenticated user + $user = $this->getUser(); + $userHeaders = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user['session'], + ]; + + // Create multiple transactions for this user + $transactionIds = []; + for ($i = 0; $i < 3; $i++) { + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $this->assertNotEmpty($transaction['body']['$id']); + $transactionIds[] = $transaction['body']['$id']; + } + + // List transactions + $list = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(200, $list['headers']['status-code']); + $this->assertGreaterThanOrEqual(3, $list['body']['total']); + $this->assertIsArray($list['body']['transactions']); + $this->assertGreaterThanOrEqual(3, count($list['body']['transactions'])); + + // Verify all created transactions are in the list + $listedIds = array_column($list['body']['transactions'], '$id'); + foreach ($transactionIds as $transactionId) { + $this->assertContains($transactionId, $listedIds); + } + + // Verify transaction structure + foreach ($list['body']['transactions'] as $transaction) { + $this->assertArrayHasKey('$id', $transaction); + $this->assertArrayHasKey('$createdAt', $transaction); + $this->assertArrayHasKey('$updatedAt', $transaction); + $this->assertArrayHasKey('status', $transaction); + $this->assertArrayHasKey('operations', $transaction); + } + } + + /** + * Test that an authenticated user can successfully delete their own transaction + */ + public function testAuthenticatedUserCanDeleteTheirOwnTransaction(): void + { + // Create an authenticated user + $user = $this->getUser(); + $userHeaders = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $user['session'], + ]; + + // Create a transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Verify transaction exists by reading it + $read = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(200, $read['headers']['status-code']); + $this->assertEquals($transactionId, $read['body']['$id']); + + // Delete the transaction + $delete = $this->client->call(Client::METHOD_DELETE, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(204, $delete['headers']['status-code']); + + // Verify transaction is deleted by trying to read it again + $readAfterDelete = $this->client->call(Client::METHOD_GET, '/tablesdb/transactions/' . $transactionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(404, $readAfterDelete['headers']['status-code']); + + // Create another transaction and verify it can also be deleted + $transaction2 = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(201, $transaction2['headers']['status-code']); + $transactionId2 = $transaction2['body']['$id']; + + $delete2 = $this->client->call(Client::METHOD_DELETE, '/tablesdb/transactions/' . $transactionId2, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $userHeaders)); + + $this->assertEquals(204, $delete2['headers']['status-code']); + } } From 276b1357997d47f3363a5a96bc4505c00215feb8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 02:17:42 +1300 Subject: [PATCH 311/385] Trigger CI From cdac840071b500dc9c2498f24285bd849610a00a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 15:51:26 +1300 Subject: [PATCH 312/385] Use return value for write ops count --- .../Http/Databases/Transactions/Update.php | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 927adb9bc7..26fd631f3a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -167,8 +167,10 @@ class Update extends Action } } - $totalOperations++; - $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + 1; + if (!\in_array($action, ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) { + $totalOperations++; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + 1; + } if ($data instanceof Document) { $data = $data->getArrayCopy(); @@ -194,16 +196,24 @@ class Update extends Action $this->handleDecrementOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'bulkCreate': - $this->handleBulkCreateOperation($dbForProject, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkCreateOperation($dbForProject, $collectionId, $data, $createdAt, $state); + $totalOperations += $count; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkUpdate': - $this->handleBulkUpdateOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkUpdateOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $totalOperations += $count; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkUpsert': - $this->handleBulkUpsertOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkUpsertOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $totalOperations += $count; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkDelete': - $this->handleBulkDeleteOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkDeleteOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); + $totalOperations += $count; + $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; } } @@ -645,7 +655,7 @@ class Update extends Action * @param array $data * @param \DateTime $createdAt * @param array &$state - * @return void + * @return int Number of documents created * @throws \Utopia\Database\Exception */ private function handleBulkCreateOperation( @@ -654,13 +664,14 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state) { + ): int { + $count = 0; + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $data, &$state, &$count) { $documents = \array_map(function ($doc) { return $doc instanceof Document ? $doc : new Document($doc); }, $data); - $dbForProject->createDocuments( + $count = $dbForProject->createDocuments( $collectionId, $documents, onNext: function (Document $document) use (&$state, $collectionId) { @@ -668,6 +679,7 @@ class Update extends Action } ); }); + return $count; } /** @@ -679,7 +691,7 @@ class Update extends Action * @param array $data * @param \DateTime $createdAt * @param array &$state - * @return void + * @return int Number of documents updated * @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception\Query * @throws ConflictException @@ -691,7 +703,7 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): int { $queries = Query::parseQueries($data['queries'] ?? []); $updateData = new Document($data['data']); @@ -701,7 +713,7 @@ class Update extends Action // Clone the document before passing to updateDocuments to prevent mutation // The database layer mutates the input document, which would corrupt transaction state - $dbForProject->updateDocuments( + $count = $dbForProject->updateDocuments( $collectionId, clone $updateData, $queries, @@ -739,6 +751,8 @@ class Update extends Action ); } } + + return $count; } /** @@ -750,7 +764,7 @@ class Update extends Action * @param array $data * @param \DateTime $createdAt * @param array &$state - * @return void + * @return int Number of documents upserted * @throws ConflictException * @throws \Utopia\Database\Exception */ @@ -761,14 +775,14 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): int { $documents = \array_map(function ($doc) { return $doc instanceof Document ? $doc : new Document($doc); }, $data); $mergedDocuments = $transactionState->applyBulkUpsertToState($collectionId, $documents, $state); - $dbForProject->upsertDocuments( + $count = $dbForProject->upsertDocuments( $collectionId, $mergedDocuments, onNext: function (Document $upserted, ?Document $old) use (&$state, $collectionId, $createdAt) { @@ -786,6 +800,8 @@ class Update extends Action $state[$collectionId][$upserted->getId()] = $upserted; } ); + + return $count; } /** @@ -797,7 +813,7 @@ class Update extends Action * @param array $data * @param \DateTime $createdAt * @param array &$state - * @return void + * @return int Number of documents deleted * @throws \Utopia\Database\Exception\Query * @throws ConflictException * @throws \Utopia\Database\Exception @@ -809,10 +825,10 @@ class Update extends Action array $data, \DateTime $createdAt, array &$state - ): void { + ): int { $queries = Query::parseQueries($data['queries'] ?? []); - $dbForProject->deleteDocuments( + $count = $dbForProject->deleteDocuments( $collectionId, $queries, onNext: function (Document $deleted, Document $old) use (&$state, $collectionId, $createdAt) { @@ -832,5 +848,7 @@ class Update extends Action ); $transactionState->applyBulkDeleteToState($collectionId, $queries, $state); + + return $count; } } From 7512a0db6b8b9bb68b1c28ea268c1e4ad73462e6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 17:27:09 +1300 Subject: [PATCH 313/385] Fix cross-API compat --- .../Collections/Documents/Action.php | 25 ++ .../Documents/Attribute/Decrement.php | 5 +- .../Documents/Attribute/Increment.php | 5 +- .../Collections/Documents/Create.php | 3 +- .../Collections/Documents/Update.php | 3 +- .../Collections/Documents/Upsert.php | 3 +- .../Http/Databases/Transactions/Update.php | 36 ++- .../Legacy/Transactions/TransactionsBase.php | 143 ++++++++++ .../Transactions/TransactionsBase.php | 246 ++++++++++++++++++ 9 files changed, 456 insertions(+), 13 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index 3d95c67a26..3da89f352c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -205,6 +205,31 @@ abstract class Action extends AppwriteAction return $this->isCollectionsAPI() ? 'collection' : 'table'; } + /** + * Get the correct attribute/column key for increment/decrement operations. + */ + protected function getAttributeKey(): string + { + return $this->isCollectionsAPI() ? 'attribute' : 'column'; + } + + /** + * Get the key used in ID parameters (e.g., 'collectionId' or 'tableId'). + */ + protected function getGroupId(): string + { + return $this->getCollectionsEventsContext() . 'Id'; + } + + /** + * Get the resource ID key for the current action. + */ + protected function getResourceId(): string + { + $resource = $this->isCollectionsAPI() ? 'document' : 'row'; + return $resource . 'Id'; + } + /** * Remove configured removable attributes from a document. * Used for relationship path handling to remove API-specific attributes. 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 316d0f1edb..0b3d0e9fb4 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 @@ -136,7 +136,7 @@ class Decrement extends Action 'documentId' => $documentId, 'action' => 'decrement', 'data' => [ - 'attribute' => $attribute, + $this->getAttributeKey() => $attribute, 'value' => $value, 'min' => $min, ], @@ -153,9 +153,10 @@ class Decrement extends Action }); // Return successful response without actually decrementing + $groupId = $this->getGroupId(); $mockDocument = new Document([ '$id' => $documentId, - '$collectionId' => $collectionId, + '$' . $groupId => $collectionId, '$databaseId' => $databaseId, $attribute => $value, ]); 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 22fc1a8152..ef64099fa8 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 @@ -136,7 +136,7 @@ class Increment extends Action 'documentId' => $documentId, 'action' => 'increment', 'data' => [ - 'attribute' => $attribute, + $this->getAttributeKey() => $attribute, 'value' => $value, 'max' => $max, ], @@ -153,9 +153,10 @@ class Increment extends Action }); // Return successful response without actually incrementing + $groupId = $this->getGroupId(); $mockDocument = new Document([ '$id' => $documentId, - '$collectionId' => $collectionId, + '$' . $groupId => $collectionId, '$databaseId' => $databaseId, $attribute => $value, ]); 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 4254283432..521190d3dc 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 @@ -419,9 +419,10 @@ class Create extends Action 'total' => \count($documents), ]), $this->getBulkResponseModel()); } else { + $groupId = $this->getGroupId(); $mockDocument = new Document([ '$id' => $documents[0]['$id'] ?? $documentId, - '$collectionId' => $collectionId, + '$' . $groupId => $collectionId, '$databaseId' => $databaseId, ...$documents[0] ]); 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 a3a976c04a..552c51a5fb 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 @@ -292,9 +292,10 @@ class Update extends Action }); // Return successful response without actually updating document + $groupId = $this->getGroupId(); $mockDocument = new Document([ '$id' => $documentId, - '$collectionId' => $collectionId, + '$' . $groupId => $collectionId, '$databaseId' => $databaseId, ...$document->getArrayCopy(), ...$data diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 91a35d74c2..f113a99c7a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -301,9 +301,10 @@ class Upsert extends Action }); // Return successful response without actually upserting document + $groupId = $this->getGroupId(); $mockDocument = new Document([ '$id' => $documentId, - '$collectionId' => $collectionId, + '$' . $groupId => $collectionId, '$databaseId' => $databaseId, ...$data ]); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 26fd631f3a..6e2bd63827 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -557,6 +557,28 @@ class Update extends Action }); } + /** + * Get the attribute/column name from data, with fallback for cross-API compatibility + * + * @param array $data The operation data + * @return string The attribute/column name + */ + private function getAttributeNameFromData(array $data): string + { + $expectedKey = $this->getAttributeKey(); + if (isset($data[$expectedKey])) { + return $data[$expectedKey]; + } + + // Try the opposite key for cross-API compatibility + $fallbackKey = $expectedKey === 'attribute' ? 'column' : 'attribute'; + if (isset($data[$fallbackKey])) { + return $data[$fallbackKey]; + } + + return ''; + } + /** * Handle increment operation * @@ -579,23 +601,24 @@ class Update extends Action array &$state ): void { $dependent = isset($state[$collectionId][$documentId]); + $attribute = $this->getAttributeNameFromData($data); if ($dependent) { $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data[$this->getAttributeKey()], + attribute: $attribute, value: $data['value'] ?? 1, max: $data['max'] ?? null ); return; } - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state, $attribute) { $state[$collectionId][$documentId] = $dbForProject->increaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data[$this->getAttributeKey()], + attribute: $attribute, value: $data['value'] ?? 1, max: $data['max'] ?? null ); @@ -624,23 +647,24 @@ class Update extends Action array &$state ): void { $dependent = isset($state[$collectionId][$documentId]); + $attribute = $this->getAttributeNameFromData($data); if ($dependent) { $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data[$this->getAttributeKey()], + attribute: $attribute, value: $data['value'] ?? 1, min: $data['min'] ?? null ); return; } - $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state) { + $dbForProject->withRequestTimestamp($createdAt, function () use ($dbForProject, $collectionId, $documentId, $data, &$state, $attribute) { $state[$collectionId][$documentId] = $dbForProject->decreaseDocumentAttribute( collection: $collectionId, id: $documentId, - attribute: $data[$this->getAttributeKey()], + attribute: $attribute, value: $data['value'] ?? 1, min: $data['min'] ?? null ); diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php index 836f1ca966..0f85de0ff5 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php @@ -3732,6 +3732,149 @@ trait TransactionsBase $this->assertEquals(0, $doc['body']['score']); } + /** + * Test individual increment/decrement endpoints with transactions for Legacy Collections API + * This test ensures that: + * 1. Transaction logs store the correct attribute key ('attribute' for Collections API) + * 2. Mock responses return the correct ID keys ('$collectionId' not '$tableId') + */ + public function testIncrementDecrementEndpointsWithTransaction(): void + { + // Create database and collection + $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IncrDecrEndpointTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $collection = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'AccountsCollection', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $collectionId = $collection['body']['$id']; + + // Add balance attribute + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/attributes/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'balance', + 'required' => false, + 'default' => 0, + ]); + + sleep(2); + + // Create initial documents + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'joe', + 'data' => ['balance' => 100] + ]); + + $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'jane', + 'data' => ['balance' => 50] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test: Decrement using individual endpoint - should store 'attribute' not 'column' in transaction log + $decrementResponse = $this->client->call( + Client::METHOD_PATCH, + "/databases/{$databaseId}/collections/{$collectionId}/documents/joe/balance/decrement", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'transactionId' => $transactionId, + 'value' => 50, + 'min' => 0, + ] + ); + + // Test: Response should return '$collectionId' not '$tableId' for Collections API + $this->assertEquals(200, $decrementResponse['headers']['status-code']); + $this->assertArrayHasKey('$collectionId', $decrementResponse['body'], 'Response should contain $collectionId for Collections API'); + $this->assertArrayNotHasKey('$tableId', $decrementResponse['body'], 'Response should not contain $tableId for Collections API'); + $this->assertEquals($collectionId, $decrementResponse['body']['$collectionId']); + + // Test increment endpoint + $incrementResponse = $this->client->call( + Client::METHOD_PATCH, + "/databases/{$databaseId}/collections/{$collectionId}/documents/jane/balance/increment", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'transactionId' => $transactionId, + 'value' => 50, + ] + ); + + $this->assertEquals(200, $incrementResponse['headers']['status-code']); + $this->assertArrayHasKey('$collectionId', $incrementResponse['body'], 'Response should contain $collectionId for Collections API'); + $this->assertArrayNotHasKey('$tableId', $incrementResponse['body'], 'Response should not contain $tableId for Collections API'); + $this->assertEquals($collectionId, $incrementResponse['body']['$collectionId']); + + // Commit transaction - this will fail if transaction log has 'column' instead of 'attribute' + $commitResponse = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(200, $commitResponse['headers']['status-code'], 'Transaction commit should succeed'); + + // Verify final values + $joe = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/joe", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $jane = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents/jane", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $joe['headers']['status-code']); + $this->assertEquals(50, $joe['body']['balance'], 'Joe should have 100 - 50 = 50'); + + $this->assertEquals(200, $jane['headers']['status-code']); + $this->assertEquals(100, $jane['body']['balance'], 'Jane should have 50 + 50 = 100'); + } + /** * Test bulk update operations in transaction */ diff --git a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php index b64d249e97..efa3b52cef 100644 --- a/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsBase.php @@ -3867,6 +3867,252 @@ trait TransactionsBase $this->assertEquals('updated', $row['body']['status'], 'Status should be updated'); } + /** + * Test individual increment/decrement endpoints with transactions + * This test ensures that: + * 1. Transaction logs store the correct attribute key ('column' for TablesDB) + * 2. Mock responses return the correct ID keys ('$tableId' not '$collectionId') + */ + public function testIncrementDecrementEndpointsWithTransaction(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'IncrDecrEndpointTestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'AccountsTable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add balance column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'balance', + 'required' => false, + 'default' => 0, + ]); + + sleep(2); + + // Create initial rows + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'joe', + 'data' => ['balance' => 100] + ]); + + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'jane', + 'data' => ['balance' => 50] + ]); + + // Create transaction + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(201, $transaction['headers']['status-code']); + $transactionId = $transaction['body']['$id']; + + // Test Bug 1: Decrement using individual endpoint - should store 'column' not 'attribute' in transaction log + $decrementResponse = $this->client->call( + Client::METHOD_PATCH, + "/tablesdb/{$databaseId}/tables/{$tableId}/rows/joe/balance/decrement", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'transactionId' => $transactionId, + 'value' => 50, + 'min' => 0, + ] + ); + + // Test Bug 2: Response should return '$tableId' not '$collectionId' + $this->assertEquals(200, $decrementResponse['headers']['status-code']); + $this->assertArrayHasKey('$tableId', $decrementResponse['body'], 'Response should contain $tableId for TablesDB API'); + $this->assertArrayNotHasKey('$collectionId', $decrementResponse['body'], 'Response should not contain $collectionId for TablesDB API'); + $this->assertEquals($tableId, $decrementResponse['body']['$tableId']); + + // Test increment endpoint + $incrementResponse = $this->client->call( + Client::METHOD_PATCH, + "/tablesdb/{$databaseId}/tables/{$tableId}/rows/jane/balance/increment", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'transactionId' => $transactionId, + 'value' => 50, + ] + ); + + $this->assertEquals(200, $incrementResponse['headers']['status-code']); + $this->assertArrayHasKey('$tableId', $incrementResponse['body'], 'Response should contain $tableId for TablesDB API'); + $this->assertArrayNotHasKey('$collectionId', $incrementResponse['body'], 'Response should not contain $collectionId for TablesDB API'); + $this->assertEquals($tableId, $incrementResponse['body']['$tableId']); + + // Commit transaction - this will fail if transaction log has 'attribute' instead of 'column' + $commitResponse = $this->client->call(Client::METHOD_PATCH, "/tablesdb/transactions/{$transactionId}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'commit' => true + ]); + + $this->assertEquals(200, $commitResponse['headers']['status-code'], 'Transaction commit should succeed'); + + // Verify final values + $joe = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/joe", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $jane = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/jane", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $joe['headers']['status-code']); + $this->assertEquals(50, $joe['body']['balance'], 'Joe should have 100 - 50 = 50'); + + $this->assertEquals(200, $jane['headers']['status-code']); + $this->assertEquals(100, $jane['body']['balance'], 'Jane should have 50 + 50 = 100'); + } + + /** + * Test cross-API compatibility: stage operations via TablesDB, commit via Collections API + * This ensures fallback logic works when APIs are mixed + */ + public function testCrossAPIIncrementDecrement(): void + { + // Create database and table + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'databaseId' => ID::unique(), + 'name' => 'CrossAPITestDB' + ]); + + $databaseId = $database['body']['$id']; + + $table = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'CrossAPITable', + 'permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + ]); + + $tableId = $table['body']['$id']; + + // Add balance column + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/columns/integer", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'balance', + 'required' => false, + 'default' => 0, + ]); + + sleep(2); + + // Create initial row + $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => 'test', + 'data' => ['balance' => 100] + ]); + + // Create transaction using TablesDB API + $transaction = $this->client->call(Client::METHOD_POST, '/tablesdb/transactions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $transactionId = $transaction['body']['$id']; + + // Stage operations using TablesDB API (will store 'column' key) + $this->client->call( + Client::METHOD_PATCH, + "/tablesdb/{$databaseId}/tables/{$tableId}/rows/test/balance/decrement", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'transactionId' => $transactionId, + 'value' => 30, + ] + ); + + // Commit using Collections API (expects 'attribute' key but should fallback to 'column') + $commitResponse = $this->client->call( + Client::METHOD_PATCH, + "/databases/transactions/{$transactionId}", + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'commit' => true + ] + ); + + $this->assertEquals(200, $commitResponse['headers']['status-code'], 'Cross-API commit should succeed'); + + // Verify final value + $row = $this->client->call(Client::METHOD_GET, "/tablesdb/{$databaseId}/tables/{$tableId}/rows/test", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $row['headers']['status-code']); + $this->assertEquals(70, $row['body']['balance'], 'Balance should be 100 - 30 = 70'); + } + public function testBulkUpdateWithDependentDocuments(): void { // Create database and table From f95e8a965a337caec26d0b547734ea82c89202f1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 17:27:57 +1300 Subject: [PATCH 314/385] Only set sequence on not empty --- src/Appwrite/Utopia/Response/Model/Document.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Document.php b/src/Appwrite/Utopia/Response/Model/Document.php index 5bad504a63..0faebf2d76 100644 --- a/src/Appwrite/Utopia/Response/Model/Document.php +++ b/src/Appwrite/Utopia/Response/Model/Document.php @@ -82,7 +82,10 @@ class Document extends Any { $document->removeAttribute('$collection'); $document->removeAttribute('$tenant'); - $document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0)); + + if (!$document->isEmpty()) { + $document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0)); + } foreach ($document->getAttributes() as $attribute) { if (\is_array($attribute)) { From 2830ab55f05ddfc19970989dcdaeb387cb209192 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 17:42:42 +1300 Subject: [PATCH 315/385] Lint --- src/Appwrite/Utopia/Response/Model/Document.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Document.php b/src/Appwrite/Utopia/Response/Model/Document.php index 0faebf2d76..f9766c895c 100644 --- a/src/Appwrite/Utopia/Response/Model/Document.php +++ b/src/Appwrite/Utopia/Response/Model/Document.php @@ -82,7 +82,7 @@ class Document extends Any { $document->removeAttribute('$collection'); $document->removeAttribute('$tenant'); - + if (!$document->isEmpty()) { $document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0)); } From 12a1653ae00c7876f781ef6d402734273cdc2435 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 18:44:48 +1300 Subject: [PATCH 316/385] Update doc desc --- .../Databases/Http/Databases/Transactions/Operations/Create.php | 2 +- .../Databases/Http/TablesDB/Transactions/Operations/Create.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 8aa132d15d..bd94c1c7eb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -39,7 +39,7 @@ class Create extends Action $this ->setHttpMethod(self::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/databases/transactions/:transactionId/operations') - ->desc('Create operations scoped to a transaction') + ->desc('Create operations') ->groups(['api', 'database', 'transactions']) ->label('scope', 'documents.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index d85020b2bc..469eb1b792 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -30,7 +30,7 @@ class Create extends OperationsCreate $this ->setHttpMethod(self::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/tablesdb/transactions/:transactionId/operations') - ->desc('Create operations scoped to a transaction') + ->desc('Create operations') ->groups(['api', 'database', 'transactions']) ->label('scope', 'rows.write') ->label('resourceType', RESOURCE_TYPE_DATABASES) From ab5556919e6c8bc01e4d395d8c0412b8dc119579 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Oct 2025 19:21:33 +1300 Subject: [PATCH 317/385] Update specs --- app/config/specs/open-api3-1.8.x-client.json | 4 ++-- app/config/specs/open-api3-1.8.x-console.json | 4 ++-- app/config/specs/open-api3-1.8.x-server.json | 4 ++-- app/config/specs/open-api3-latest-client.json | 4 ++-- app/config/specs/open-api3-latest-console.json | 4 ++-- app/config/specs/open-api3-latest-server.json | 4 ++-- app/config/specs/swagger2-1.8.x-client.json | 4 ++-- app/config/specs/swagger2-1.8.x-console.json | 4 ++-- app/config/specs/swagger2-1.8.x-server.json | 4 ++-- app/config/specs/swagger2-latest-client.json | 4 ++-- app/config/specs/swagger2-latest-console.json | 4 ++-- app/config/specs/swagger2-latest-server.json | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/config/specs/open-api3-1.8.x-client.json b/app/config/specs/open-api3-1.8.x-client.json index bf9f8bbece..b6a6a1afe3 100644 --- a/app/config/specs/open-api3-1.8.x-client.json +++ b/app/config/specs/open-api3-1.8.x-client.json @@ -5247,7 +5247,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -8389,7 +8389,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-1.8.x-console.json b/app/config/specs/open-api3-1.8.x-console.json index 73b8ebb1de..c086fe03d0 100644 --- a/app/config/specs/open-api3-1.8.x-console.json +++ b/app/config/specs/open-api3-1.8.x-console.json @@ -5646,7 +5646,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -33781,7 +33781,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-1.8.x-server.json b/app/config/specs/open-api3-1.8.x-server.json index b345a36501..49adaeeefa 100644 --- a/app/config/specs/open-api3-1.8.x-server.json +++ b/app/config/specs/open-api3-1.8.x-server.json @@ -5198,7 +5198,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -24311,7 +24311,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index bf9f8bbece..b6a6a1afe3 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -5247,7 +5247,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -8389,7 +8389,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 73b8ebb1de..c086fe03d0 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -5646,7 +5646,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -33781,7 +33781,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index b345a36501..49adaeeefa 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -5198,7 +5198,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "tags": [ "databases" @@ -24311,7 +24311,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "tags": [ "tablesDB" diff --git a/app/config/specs/swagger2-1.8.x-client.json b/app/config/specs/swagger2-1.8.x-client.json index 756d19fd53..89fc5e7e5c 100644 --- a/app/config/specs/swagger2-1.8.x-client.json +++ b/app/config/specs/swagger2-1.8.x-client.json @@ -5386,7 +5386,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -8463,7 +8463,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-1.8.x-console.json b/app/config/specs/swagger2-1.8.x-console.json index df5d64cb8d..098f138b30 100644 --- a/app/config/specs/swagger2-1.8.x-console.json +++ b/app/config/specs/swagger2-1.8.x-console.json @@ -5805,7 +5805,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -33897,7 +33897,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-1.8.x-server.json b/app/config/specs/swagger2-1.8.x-server.json index 345b787f79..9c8ef73243 100644 --- a/app/config/specs/swagger2-1.8.x-server.json +++ b/app/config/specs/swagger2-1.8.x-server.json @@ -5345,7 +5345,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -24483,7 +24483,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 756d19fd53..89fc5e7e5c 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -5386,7 +5386,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -8463,7 +8463,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index df5d64cb8d..098f138b30 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -5805,7 +5805,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -33897,7 +33897,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 345b787f79..9c8ef73243 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -5345,7 +5345,7 @@ }, "\/databases\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "databasesCreateOperations", "consumes": [ "application\/json" @@ -24483,7 +24483,7 @@ }, "\/tablesdb\/transactions\/{transactionId}\/operations": { "post": { - "summary": "Create operations scoped to a transaction", + "summary": "Create operations", "operationId": "tablesDBCreateOperations", "consumes": [ "application\/json" From d6fc2806dcada52335eec9b7efbd42624e57c485 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 10 Oct 2025 13:26:55 +0530 Subject: [PATCH 318/385] fix: prevent empty releases in sdk release script --- src/Appwrite/Platform/Tasks/SDKs.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 87c70d214b..7bd3deabac 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -280,6 +280,20 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND continue; } + // Check if the latest commit on the target branch already has a release + $latestCommitCommand = 'gh api repos/' . $repoName . '/commits/' . $releaseTarget . ' --jq ".sha" 2>/dev/null'; + $latestCommitSha = trim(\shell_exec($latestCommitCommand) ?? ''); + + if (!empty($latestCommitSha)) { + $commitReleasesCommand = 'gh api repos/' . $repoName . '/releases --jq ".[] | select(.target_commitish == \"' . $releaseTarget . '\") | .tag_name" 2>/dev/null | head -n 1'; + $existingCommitRelease = trim(\shell_exec($commitReleasesCommand) ?? ''); + + if (!empty($existingCommitRelease)) { + Console::warning("Latest commit on {$releaseTarget} already has a release ({$existingCommitRelease}) for {$language['name']} SDK, skipping to avoid empty release..."); + continue; + } + } + $previousVersion = ''; $tagListCommand = 'gh release list --repo "' . $repoName . '" --limit 1 --json tagName --jq ".[0].tagName" 2>&1'; $previousVersion = trim(\shell_exec($tagListCommand) ?? ''); From 1f322a270045f452690648df9562483282dfd193 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Fri, 10 Oct 2025 15:30:56 +0530 Subject: [PATCH 319/385] add test --- .../Functions/FunctionsCustomClientTest.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index f8b7bae325..c3c9fbfbab 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -32,6 +32,41 @@ class FunctionsCustomClientTest extends Scope 'timeout' => 10, ]); $this->assertEquals(401, $function['headers']['status-code']); + + + /** + * Test for DUPLICATE functionId + */ + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'execute' => [Role::user($this->getUser()['$id'])->toString()], + 'runtime' => 'node-22', + 'entrypoint' => 'index.js', + 'events' => [ + 'users.*.create', + 'users.*.delete', + ], + 'timeout' => 10, + ]); + + $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'functionId' => $functionId, + 'name' => 'Test', + 'execute' => [Role::user($this->getUser()['$id'])->toString()], + 'runtime' => 'node-22', + 'entrypoint' => 'index.js', + 'events' => [ + 'users.*.create', + 'users.*.delete', + ], + 'timeout' => 10, + ]); + $this->assertEquals(409, $response['headers']['status-code']); } public function testCreateExecution() From 751d51ea2ebf9576fad0c3c8c984663f571c104d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 10 Oct 2025 15:37:29 +0530 Subject: [PATCH 320/385] update domains lib to 0.8.2 --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 1afa3f5109..908a4d9d99 100644 --- a/composer.lock +++ b/composer.lock @@ -3792,16 +3792,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.1", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "d5f903e93c105407da6374e411c4805b7decd8a8" + "reference": "caa294dcebd05c8af876c8afef3e992faccdf645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/d5f903e93c105407da6374e411c4805b7decd8a8", - "reference": "d5f903e93c105407da6374e411c4805b7decd8a8", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/caa294dcebd05c8af876c8afef3e992faccdf645", + "reference": "caa294dcebd05c8af876c8afef3e992faccdf645", "shasum": "" }, "require": { @@ -3847,9 +3847,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.1" + "source": "https://github.com/utopia-php/domains/tree/0.8.2" }, - "time": "2025-10-03T11:58:53+00:00" + "time": "2025-10-06T09:56:54+00:00" }, { "name": "utopia-php/dsn", From 1c6b56385c2a6a70a0c9b919af15db8562bede51 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 10 Oct 2025 18:26:00 +0530 Subject: [PATCH 321/385] fix: code 0 from databases. --- app/realtime.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index fccf5c9a20..e18ab8e10d 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -606,8 +606,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $message = $th->getMessage(); - // sanitize 5xx errors - if ($code >= 500 && !App::isDevelopment()) { + // sanitize 0 && 5xx errors + if (($code === 0 || $code >= 500) && !App::isDevelopment()) { $message = 'Error: Server Error'; } @@ -719,8 +719,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $message = $th->getMessage(); - // sanitize 5xx errors - if ($code >= 500 && !App::isDevelopment()) { + // sanitize 0 && 5xx errors + if (($code === 0 || $code >= 500) && !App::isDevelopment()) { $message = 'Error: Server Error'; } From 1b17c32405e141814257747a3efb14d84c8df9fd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 12 Oct 2025 03:51:37 +0000 Subject: [PATCH 322/385] update block --- app/init/resources.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index cdec170168..733a01133f 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -298,7 +298,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); } - if (APP_MODE_ADMIN !== $mode) { + if (APP_MODE_ADMIN === $mode) { + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); + } else { if ($project->isEmpty()) { $user = new Document([]); } else { @@ -308,8 +310,6 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); } } - } else { - $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); } if ( From 5e5b22d64945708fc0b860cccb7d97dc6a12031c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 12 Oct 2025 04:38:14 +0000 Subject: [PATCH 323/385] fix jwt --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2b91c46254..bd69350500 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2823,7 +2823,7 @@ App::post('/v1/account/jwts') ->dynamic(new Document([ 'jwt' => $jwt->encode([ 'userId' => $user->getId(), - 'sessionId' => $current->getId(), + 'sessionId' => $sessionId, ]) ]), Response::MODEL_JWT); }); From 935011b9e9e6b0ddb43df9b64c4d8560ddc114cb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 13 Oct 2025 15:59:26 +1300 Subject: [PATCH 324/385] Fix backwards compat API scopes --- .../Modules/Databases/Http/TablesDB/Transactions/Create.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Delete.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Get.php | 2 +- .../Databases/Http/TablesDB/Transactions/Operations/Create.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/Update.php | 2 +- .../Modules/Databases/Http/TablesDB/Transactions/XList.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php index a375d47c3c..bc79b86ca3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Create.php @@ -30,7 +30,7 @@ class Create extends TransactionsCreate ->setHttpPath('/v1/tablesdb/transactions') ->desc('Create transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.write') + ->label('scope', ['documents.write', 'rows.write']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php index 46cd6b6c51..6f92a1b10b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Delete.php @@ -30,7 +30,7 @@ class Delete extends TransactionsDelete ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Delete transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.write') + ->label('scope', ['documents.write', 'rows.write']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php index bc58783a04..ab7925c916 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Get.php @@ -30,7 +30,7 @@ class Get extends TransactionsGet ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Get transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.read') + ->label('scope', ['documents.read', 'rows.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php index 469eb1b792..5a98f22f37 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Operations/Create.php @@ -32,7 +32,7 @@ class Create extends OperationsCreate ->setHttpPath('/v1/tablesdb/transactions/:transactionId/operations') ->desc('Create operations') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.write') + ->label('scope', ['documents.write', 'rows.write']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index 72a6a9da6f..4d55af93a4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -31,7 +31,7 @@ class Update extends TransactionsUpdate ->setHttpPath('/v1/tablesdb/transactions/:transactionId') ->desc('Update transaction') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.write') + ->label('scope', ['documents.write', 'rows.write']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php index cfb630e46d..9a9c22a1a8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/XList.php @@ -30,7 +30,7 @@ class XList extends TransactionsList ->setHttpPath('/v1/tablesdb/transactions') ->desc('List transactions') ->groups(['api', 'database', 'transactions']) - ->label('scope', 'rows.read') + ->label('scope', ['documents.read', 'rows.read']) ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk', new Method( namespace: 'tablesDB', From 3b82141de227aa3aedeb21e391d529bb33479b7f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Oct 2025 15:25:51 +0530 Subject: [PATCH 325/385] Update .NET SDK to 0.21.2 and improve release detection - Update .NET SDK version to 0.21.2 with Object[] deserialization fix - Update sdk-generator dependency from 1.4.3 to 1.4.4 - Improve SDK release detection to check actual commit SHA of latest release tag instead of just checking releases targeting the branch --- app/config/platforms.php | 2 +- composer.lock | 12 ++++++------ docs/sdks/dotnet/CHANGELOG.md | 4 ++++ src/Appwrite/Platform/Tasks/SDKs.php | 17 +++++++++++------ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 22606d803c..808ad486b7 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.21.1', + 'version' => '0.21.2', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 908a4d9d99..b544b18aab 100644 --- a/composer.lock +++ b/composer.lock @@ -5004,16 +5004,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3" + "reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", - "reference": "e1ca749398189f36ec6d6afb8e9f64e9cb37e0a3", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49", + "reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49", "shasum": "" }, "require": { @@ -5049,9 +5049,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.3" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.4" }, - "time": "2025-10-01T06:25:19+00:00" + "time": "2025-10-13T09:20:49+00:00" }, { "name": "doctrine/annotations", diff --git a/docs/sdks/dotnet/CHANGELOG.md b/docs/sdks/dotnet/CHANGELOG.md index deb467ce3d..dfd28ad686 100644 --- a/docs/sdks/dotnet/CHANGELOG.md +++ b/docs/sdks/dotnet/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 0.21.2 + +* Fix: handle Object[] during array deserialization + ## 0.21.1 * Add transaction support for Databases and TablesDB diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 7bd3deabac..f37f04da38 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -285,12 +285,17 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $latestCommitSha = trim(\shell_exec($latestCommitCommand) ?? ''); if (!empty($latestCommitSha)) { - $commitReleasesCommand = 'gh api repos/' . $repoName . '/releases --jq ".[] | select(.target_commitish == \"' . $releaseTarget . '\") | .tag_name" 2>/dev/null | head -n 1'; - $existingCommitRelease = trim(\shell_exec($commitReleasesCommand) ?? ''); - - if (!empty($existingCommitRelease)) { - Console::warning("Latest commit on {$releaseTarget} already has a release ({$existingCommitRelease}) for {$language['name']} SDK, skipping to avoid empty release..."); - continue; + $latestReleaseTagCommand = 'gh api repos/' . $repoName . '/releases --jq ".[0] | .tag_name" 2>/dev/null'; + $latestReleaseTag = trim(\shell_exec($latestReleaseTagCommand) ?? ''); + + if (!empty($latestReleaseTag)) { + $tagCommitCommand = 'gh api repos/' . $repoName . '/git/ref/tags/' . $latestReleaseTag . ' --jq ".object.sha" 2>/dev/null'; + $tagCommitSha = trim(\shell_exec($tagCommitCommand) ?? ''); + + if (!empty($tagCommitSha) && $latestCommitSha === $tagCommitSha) { + Console::warning("Latest commit on {$releaseTarget} already has a release ({$latestReleaseTag}) for {$language['name']} SDK, skipping to avoid empty release..."); + continue; + } } } From 6f5ac232c5e69c6dc0a0bef58663741055cd2d62 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Oct 2025 16:01:55 +0530 Subject: [PATCH 326/385] lint --- src/Appwrite/Platform/Tasks/SDKs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index f37f04da38..2fb15c5f7d 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -287,11 +287,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND if (!empty($latestCommitSha)) { $latestReleaseTagCommand = 'gh api repos/' . $repoName . '/releases --jq ".[0] | .tag_name" 2>/dev/null'; $latestReleaseTag = trim(\shell_exec($latestReleaseTagCommand) ?? ''); - + if (!empty($latestReleaseTag)) { $tagCommitCommand = 'gh api repos/' . $repoName . '/git/ref/tags/' . $latestReleaseTag . ' --jq ".object.sha" 2>/dev/null'; $tagCommitSha = trim(\shell_exec($tagCommitCommand) ?? ''); - + if (!empty($tagCommitSha) && $latestCommitSha === $tagCommitSha) { Console::warning("Latest commit on {$releaseTarget} already has a release ({$latestReleaseTag}) for {$language['name']} SDK, skipping to avoid empty release..."); continue; From 354464990392a05a2072be4e554e159e078aa19e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:59:32 +0100 Subject: [PATCH 327/385] fix: block schedules --- app/cli.php | 5 ++ src/Appwrite/Platform/Tasks/ScheduleBase.php | 55 +++++++++++--------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/app/cli.php b/app/cli.php index 0f98cf3458..71b6464cb9 100644 --- a/app/cli.php +++ b/app/cli.php @@ -103,6 +103,11 @@ CLI::setResource('console', function () { return new Document(Config::getParam('console')); }, []); +CLI::setResource( + 'isResourceBlocked', + fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false +); + CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 5cd25b09b4..e9a0e1d333 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -49,6 +49,7 @@ abstract class ScheduleBase extends Action ->inject('publisherMigrations') ->inject('publisherFunctions') ->inject('publisherMessaging') + ->inject('isResourceBlocked') ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('telemetry') @@ -71,7 +72,7 @@ abstract class ScheduleBase extends Action * 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute * 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker. */ - public function action(BrokerPool $publisher, BrokerPool $publisherMigrations, BrokerPool $publisherFunctions, BrokerPool $publisherMessaging, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void + public function action(BrokerPool $publisher, BrokerPool $publisherMigrations, BrokerPool $publisherFunctions, BrokerPool $publisherMessaging, callable $isResourceBlocked, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void { Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1'); Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started'); @@ -88,16 +89,16 @@ abstract class ScheduleBase extends Action // start with "0" to load all active documents. $lastSyncUpdate = "0"; - $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate, $isResourceBlocked); Console::success("Starting timers at " . DateTime::now()); /** * The timer synchronize $schedules copy with database collection. */ - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, $getProjectDB, &$lastSyncUpdate) { + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, $getProjectDB, &$lastSyncUpdate, $isResourceBlocked) { $time = DateTime::now(); Console::log("Sync tick: Running at $time"); - $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate, $isResourceBlocked); }); while (true) { @@ -112,7 +113,7 @@ abstract class ScheduleBase extends Action } } - private function collectSchedules(Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void + private function collectSchedules(Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate, callable $isResourceBlocked): void { // If we haven't synced yet, load all active schedules $initialLoad = $lastSyncUpdate === "0"; @@ -178,34 +179,40 @@ abstract class ScheduleBase extends Action $paginationQueries[] = Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate); } - $results = $dbForPlatform->find('schedules', $paginationQueries); + $collectionId = static::getCollectionId(); + $schedules = $dbForPlatform->find('schedules', $paginationQueries); + $sum = count($schedules); + $total += $sum; - $sum = count($results); - $total = $total + $sum; + foreach ($schedules as $schedule) { + $existing = $this->schedules[$schedule->getSequence()] ?? null; + $updated = strtotime($existing['resourceUpdatedAt'] ?? '0') !== strtotime($schedule['resourceUpdatedAt'] ?? '0'); - foreach ($results as $document) { - $localDocument = $this->schedules[$document->getSequence()] ?? null; - - if ($localDocument !== null) { - if (!$document['active']) { - Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}"); - unset($this->schedules[$document->getSequence()]); - } elseif (strtotime($localDocument['resourceUpdatedAt']) !== strtotime($document['resourceUpdatedAt'])) { - Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}"); - $this->schedules[$document->getSequence()] = $getSchedule($document); - } - } else { + if ($existing === null || $updated) { try { - $this->schedules[$document->getSequence()] = $getSchedule($document); + $candidate = $getSchedule($schedule); } catch (\Throwable $th) { - $collectionId = static::getCollectionId(); - Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}"); + Console::error("Failed to load schedule for project {$schedule['projectId']} {$collectionId} {$schedule['resourceId']}"); Console::error($th->getMessage()); + continue; } + + if (!$candidate['active']) { + unset($this->schedules[$schedule->getSequence()]); + continue; + } + + if ($isResourceBlocked($candidate['project'], $collectionId, $candidate['resourceId'])) { + unset($this->schedules[$schedule->getSequence()]); + continue; + } + + Console::info("Updating: {$schedule['resourceType']}::{$schedule['resourceId']}"); + $this->schedules[$schedule->getSequence()] = $candidate; } } - $latestDocument = \end($results); + $latestDocument = \end($schedules); } $lastSyncUpdate = $time; From 128cd68ec8114bd7b98673b7efa857f4a75f839c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 14 Oct 2025 09:23:31 +0530 Subject: [PATCH 328/385] chore: use bcc only emails for smtp --- src/Appwrite/Platform/Workers/Messaging.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 668993fdae..8c62ae32b8 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -593,6 +593,19 @@ class Messaging extends Action $content = $data['content']; $html = $data['html'] ?? false; + // For SMTP, move all recipients to BCC and use default recipient in TO field + if ($provider->getAttribute('provider') === 'smtp') { + $defaultRecipient = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + + if ($defaultRecipient) { + foreach ($to as $recipient) { + $bcc[] = ['email' => $recipient]; + } + + $to = [$defaultRecipient]; + } + } + return new Email( $to, $subject, From 752520470d858fb6adea17d8b8bfef0534e31400 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 14 Oct 2025 09:27:50 +0530 Subject: [PATCH 329/385] fix: param --- src/Appwrite/Platform/Workers/Messaging.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 8c62ae32b8..1a714c232c 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -592,18 +592,15 @@ class Messaging extends Action $subject = $data['subject']; $content = $data['content']; $html = $data['html'] ?? false; + $defaultRecipient = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); // For SMTP, move all recipients to BCC and use default recipient in TO field if ($provider->getAttribute('provider') === 'smtp') { - $defaultRecipient = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); - - if ($defaultRecipient) { - foreach ($to as $recipient) { - $bcc[] = ['email' => $recipient]; - } - - $to = [$defaultRecipient]; + foreach ($to as $recipient) { + $bcc[] = ['email' => $recipient]; } + + $to = []; } return new Email( @@ -617,7 +614,8 @@ class Messaging extends Action $cc, $bcc, $attachments, - $html + $html, + $defaultRecipient ); } From 07727298f70dda8f0b89ddd430883d9b71944a0c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 14 Oct 2025 17:18:44 +0530 Subject: [PATCH 330/385] remove default recepient --- composer.json | 2 +- composer.lock | 14 +++++++------- src/Appwrite/Platform/Workers/Messaging.php | 3 --- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 9042cb088f..79dd9040a6 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "utopia-php/image": "0.8.*", "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", - "utopia-php/messaging": "0.18.*", + "utopia-php/messaging": "0.19.*", "utopia-php/migration": "1.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", diff --git a/composer.lock b/composer.lock index b544b18aab..3608d91b03 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": "773efb29b9b584b1249790e0016c823a", + "content-hash": "568800edca746c4e8d0d50648b25f589", "packages": [ { "name": "adhocore/jwt", @@ -4136,16 +4136,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.18.2", + "version": "0.19.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "0d364edacf4d4867964c7e17f653031dd39394bf" + "reference": "0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/0d364edacf4d4867964c7e17f653031dd39394bf", - "reference": "0d364edacf4d4867964c7e17f653031dd39394bf", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8", + "reference": "0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8", "shasum": "" }, "require": { @@ -4181,9 +4181,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.18.2" + "source": "https://github.com/utopia-php/messaging/tree/0.19.0" }, - "time": "2025-07-21T18:27:03+00:00" + "time": "2025-10-14T11:46:49+00:00" }, { "name": "utopia-php/migration", diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 1a714c232c..ffd71cd867 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -592,14 +592,12 @@ class Messaging extends Action $subject = $data['subject']; $content = $data['content']; $html = $data['html'] ?? false; - $defaultRecipient = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); // For SMTP, move all recipients to BCC and use default recipient in TO field if ($provider->getAttribute('provider') === 'smtp') { foreach ($to as $recipient) { $bcc[] = ['email' => $recipient]; } - $to = []; } @@ -615,7 +613,6 @@ class Messaging extends Action $bcc, $attachments, $html, - $defaultRecipient ); } From 97f448f984645358083d1fb49352cf100309c99d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 14 Oct 2025 17:19:08 +0530 Subject: [PATCH 331/385] comma --- src/Appwrite/Platform/Workers/Messaging.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index ffd71cd867..abf46e67a4 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -612,7 +612,7 @@ class Messaging extends Action $cc, $bcc, $attachments, - $html, + $html ); } From 56a15efe3f35420182ffdd08b2305951b3482b8d Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 28 Aug 2025 14:04:43 -0700 Subject: [PATCH 332/385] chore: bump appwrite version to 1.8.0 --- src/Appwrite/Migration/Migration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 2d82b9c486..588b193df4 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -89,7 +89,7 @@ abstract class Migration '1.7.2' => 'V22', '1.7.3' => 'V22', '1.7.4' => 'V22', - '1.8.0' => 'V23' + '1.8.0' => 'V23', ]; /** From 06ee42196508a17724184cf994b63436d7460ed0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 15 Oct 2025 07:11:27 +0000 Subject: [PATCH 333/385] update composer --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index ebabbeefb0..6229720bf2 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": "568800edca746c4e8d0d50648b25f589", + "content-hash": "9658991ad6520dad5807d7e1ed1e9bd4", "packages": [ { "name": "adhocore/jwt", From f062b39cfa8d2a0c96e9ae7b40347838caeffe76 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:06:01 +0000 Subject: [PATCH 334/385] Fix encoding --- app/controllers/api/account.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index bd69350500..6f12cdf8d7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -174,7 +174,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc } ; -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken) { +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) { /** @var Utopia\Database\Document $user */ $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -183,7 +183,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res throw new Exception(Exception::USER_INVALID_TOKEN); } - $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForToken); + $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForToken) + ?: Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForCode); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -2518,10 +2519,11 @@ App::put('/v1/account/sessions/magic-url') ->inject('queueForEvents') ->inject('queueForMails') ->inject('store') - ->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store) use ($createSession) { + ->inject('proofForCode') + ->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) { $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); $proofForToken->setHash(new Sha()); - $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken); + $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode); }); App::put('/v1/account/sessions/phone') @@ -2565,6 +2567,7 @@ App::put('/v1/account/sessions/phone') ->inject('queueForMails') ->inject('store') ->inject('proofForToken') + ->inject('proofForCode') ->action($createSession); App::post('/v1/account/tokens/phone') @@ -2607,8 +2610,8 @@ App::post('/v1/account/tokens/phone') ->inject('plan') ->inject('store') ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + ->inject('proofForCode') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsPassword $proofForPassword, ProofsCode $proofForCode) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -2689,7 +2692,7 @@ App::post('/v1/account/tokens/phone') } } - $secret ??= $proofForToken->generate(); + $secret ??= $proofForCode->generate(); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); $token = new Document([ @@ -2697,7 +2700,7 @@ App::post('/v1/account/tokens/phone') 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'type' => TOKEN_TYPE_PHONE, - 'secret' => $proofForToken->hash($secret), + 'secret' => $proofForCode->hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2772,6 +2775,10 @@ App::post('/v1/account/tokens/phone') ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); // Encode secret for clients + $encoded = $store + ->setProperty('id', $user->getId()) + ->setProperty('secret', $secret) + ->encode(); $token->setAttribute('secret', $encoded); $response From 4cb63068dec63227667f6fd93243eee8d03f42aa Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:07:17 +0000 Subject: [PATCH 335/385] improve install loop --- src/Appwrite/Platform/Tasks/Install.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index c70ced33ee..b210a020b9 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -150,6 +150,8 @@ class Install extends Action $input = []; + $password = new Password(); + $token = new Token(); foreach ($vars as $var) { if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) { if ($data && $var['default'] !== null) { @@ -158,13 +160,11 @@ class Install extends Action } if ($var['filter'] === 'token') { - $token = new Token(); $input[$var['name']] = $token->generate(); continue; } if ($var['filter'] === 'password') { - $password = new Password(); $input[$var['name']] = $password->generate(); continue; } From 08e559180dca64aa35fc3b02a87e102d20e00eef Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:08:07 +0000 Subject: [PATCH 336/385] fix missing injection --- app/controllers/api/account.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 6f12cdf8d7..2c0e6049d3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1211,6 +1211,7 @@ App::post('/v1/account/sessions/token') ->inject('queueForMails') ->inject('store') ->inject('proofForToken') + ->inject('proofForCode') ->action($createSession); App::get('/v1/account/sessions/oauth2/:provider') From 4d8f95095568662ea3b454976f01a44fa2d0c8a3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:17:52 +0000 Subject: [PATCH 337/385] fix roles filtering --- app/controllers/api/teams.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 1c96aa0116..387fb7d48b 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -474,9 +474,8 @@ App::post('/v1/teams/:teamId/memberships') ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('roles', [], function (Document $project) { if ($project->getId() === 'console') { - ; $roles = array_keys(Config::getParam('roles', [])); - array_filter($roles, function ($role) { + $roles = array_filter($roles, function ($role) { return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]); }); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); From 431dd96b98725fb3bca2fd5e5fdc05d1bc359ad3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:17:59 +0000 Subject: [PATCH 338/385] chaining --- app/controllers/api/users.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 536adcf128..1dfa5c2603 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1378,9 +1378,10 @@ App::patch('/v1/users/:userId/password') // Create Argon2 hasher with default settings $hasher = new Argon2(); - $hasher->setMemoryCost(2048); - $hasher->setTimeCost(4); - $hasher->setThreads(3); + $hasher + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); $newPassword = $hasher->hash($password); From 9a599e2015def129fe6096443c67fbca3166323e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:20:28 +0000 Subject: [PATCH 339/385] update recommended param for argon2 --- app/init/resources.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 733a01133f..cfd7accd35 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -978,9 +978,9 @@ App::setResource('store', function (): Store { App::setResource('proofForPassword', function (): Password { $hash = new Argon2(); $hash - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); + ->setMemoryCost(7168) + ->setTimeCost(5) + ->setThreads(1); $password = new Password(); $password From 2df621f9c5ce289f59464e0f3b7ecbe6259825a2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:23:41 +0000 Subject: [PATCH 340/385] Fix: update comment, typings --- app/init/resources.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index cfd7accd35..2062899f8b 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -24,6 +24,7 @@ use Appwrite\GraphQL\Schema; use Appwrite\Network\Platform; use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; +use Appwrite\Utopia\Response; use Executor\Executor; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; @@ -232,7 +233,7 @@ App::setResource('platforms', function (Request $request, Document $console, Doc ]; }, ['request', 'console', 'project', 'dbForPlatform']); -App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, Store $store, Token $proofForToken) { +App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ @@ -249,8 +250,8 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons * * Process: * 1. Checks the cookie based on mode: - * - If in admin mode, redirects to the console. - * - Otherwise, retrieves the project ID from the cookie. + * - If in admin mode, uses console project id for key. + * - Otherwise, sets the key using the project ID * 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`. * - If this method is used, returns the header: `X-Debug-Fallback: true`. * 3. Fetches the user document from the appropriate database based on the mode. From 22136867edcfe7150ffd15534c31a3c048fa3818 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:29:30 +0000 Subject: [PATCH 341/385] add additional check --- app/init/resources.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 2062899f8b..7e15efaf67 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -305,10 +305,12 @@ App::setResource('user', function (string $mode, Document $project, Document $co if ($project->isEmpty()) { $user = new Document([]); } else { - if ($project->getId() === 'console') { - $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); - } else { - $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); + if (!empty($store->getProperty('id', ''))) { + if ($project->getId() === 'console') { + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); + } else { + $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); + } } } } From 9849b9d678391494d069d258b5c6737c9bf43dc4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 02:41:53 +0000 Subject: [PATCH 342/385] Fix empty check --- app/init/resources.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/init/resources.php b/app/init/resources.php index 7e15efaf67..eb18ec0326 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -316,6 +316,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co } if ( + !$user || $user->isEmpty() // Check a document has been found in the DB || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) ) { // Validate user has valid login token From e06349e8036e277fd3cb782808124360d993ed4a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 03:34:45 +0000 Subject: [PATCH 343/385] update argon2 instances --- app/controllers/api/users.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 1dfa5c2603..eaa814efad 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -351,9 +351,9 @@ App::post('/v1/users/argon2') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $argon2 = new Argon2(); $argon2 - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); + ->setMemoryCost(7168) + ->setTimeCost(5) + ->setThreads(1); $user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); @@ -1379,9 +1379,9 @@ App::patch('/v1/users/:userId/password') // Create Argon2 hasher with default settings $hasher = new Argon2(); $hasher - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); + ->setMemoryCost(7168) + ->setTimeCost(5) + ->setThreads(1); $newPassword = $hasher->hash($password); From d3436077a134c4480adaa912b73842131bbba46e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 03:39:18 +0000 Subject: [PATCH 344/385] remove unused hash --- app/controllers/api/account.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2c0e6049d3..56f294a657 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2646,8 +2646,6 @@ App::post('/v1/account/tokens/phone') 'status' => true, 'password' => null, 'passwordUpdate' => null, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), 'registration' => DateTime::now(), 'reset' => false, 'prefs' => new \stdClass(), From ced2270571f058d908be4d4ff1fb3b5e2b0edd3d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 03:39:39 +0000 Subject: [PATCH 345/385] remove unused injection --- app/controllers/api/account.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 56f294a657..418770fc9c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2610,9 +2610,8 @@ App::post('/v1/account/tokens/phone') ->inject('queueForStatsUsage') ->inject('plan') ->inject('store') - ->inject('proofForPassword') ->inject('proofForCode') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsPassword $proofForPassword, ProofsCode $proofForCode) { + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } From b63c2804f1cb691e9adbf10067ee0a2e5140d289 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 10:26:49 +0545 Subject: [PATCH 346/385] Fix: undefined $sequence --- src/Appwrite/Platform/Workers/StatsUsage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 4916d0e177..32cdb02dea 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -428,7 +428,7 @@ class StatsUsage extends Action /** * Sort by unique index key reduce locks/deadlocks */ - usort($projectStats['stats'], function ($a, $b) { + usort($projectStats['stats'], function ($a, $b) use ($sequence) { // Metric DESC $cmp = strcmp($b['metric'], $a['metric']); if ($cmp !== 0) { From 7ff664e62b327d5c5530e4d2a0f54c3910e84778 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 16 Oct 2025 07:38:13 +0000 Subject: [PATCH 347/385] Fix: undefined $user --- app/init/resources.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/init/resources.php b/app/init/resources.php index eb18ec0326..4087d102ca 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -299,6 +299,8 @@ App::setResource('user', function (string $mode, Document $project, Document $co $store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); } + $user = new Document([]); + if (APP_MODE_ADMIN === $mode) { $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); } else { @@ -316,7 +318,6 @@ App::setResource('user', function (string $mode, Document $project, Document $co } if ( - !$user || $user->isEmpty() // Check a document has been found in the DB || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) ) { // Validate user has valid login token From 9ded176cf230bd19066c9836e3f11dbae943c0de Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 14:21:30 +0530 Subject: [PATCH 348/385] Refactor ProjectsConsoleClientTest to remove test dependencies - Remove @depends annotations to make tests independent - Each test now creates its own team and project instead of relying on shared state - Replace sleep() calls with assertEventually() for more reliable async testing - Change test methods to return void instead of passing data between tests - Remove unnecessary sleep() call that was causing test flakiness --- .../Projects/ProjectsConsoleClientTest.php | 195 ++++++++++++++---- 1 file changed, 152 insertions(+), 43 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index a3c8e1d2c3..a7f57140cd 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -112,13 +112,33 @@ class ProjectsConsoleClientTest extends Scope ]; } - /** - * @depends testCreateProject - */ - public function testCreateDuplicateProject($data) + public function testCreateDuplicateProject(): void { - $teamId = $data['teamId'] ?? ''; - $projectId = $data['projectId'] ?? ''; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Duplicate Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $projectId = ID::unique(); + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => $projectId, + 'name' => 'Original Project', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); /** * Test for FAILURE @@ -207,11 +227,68 @@ class ProjectsConsoleClientTest extends Scope /** * @group projectsCRUD - * @depends testCreateProject */ - public function testListProject($data): array + public function testListProject(): void { - $id = $data['projectId'] ?? ''; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Project Test', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + + // Create first project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $team['body']['$id'], + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; + + // Create second project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $team['body']['$id'], + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Create a third project with different name + $team2 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Team 1', + ]); + + $this->assertEquals(201, $team2['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Team 1 Project', + 'teamId' => $team2['body']['$id'], + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); /** * Test for SUCCESS @@ -412,16 +489,34 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - - return $data; } - /** - * @depends testCreateProject - */ - public function testGetProject($data): array + public function testGetProject(): void { - $id = $data['projectId'] ?? ''; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Get Project Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $team['body']['$id'], + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; /** * Test for SUCCESS @@ -453,8 +548,6 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(400, $response['headers']['status-code']); - - return $data; } /** @@ -542,7 +635,6 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -950,10 +1042,32 @@ class ProjectsConsoleClientTest extends Scope return ['projectId' => $projectId]; } - /** @depends testCreateProject */ - public function testUpdateProjectInvalidateSessions($data): array + public function testUpdateProjectInvalidateSessions(): void { - $id = $data['projectId']; + // Create a team for the test project + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Session Invalidation Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + + // Create a test project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Session Invalidation Test Project', + 'teamId' => $team['body']['$id'], + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; // Check defaults $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ @@ -995,8 +1109,6 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertTrue($response['body']['authInvalidateSessions']); - - return $data; } /** @@ -1457,9 +1569,6 @@ class ProjectsConsoleClientTest extends Scope $sessionCookie = $response['headers']['set-cookie']; $sessionId2 = $response['body']['$id']; - // request was called in parallel and test failed - sleep(5); - /** * List sessions */ @@ -3357,15 +3466,15 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - sleep(1); + $this->assertEventually(function () use ($id) { + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(8, $response['body']['total']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(8, $response['body']['total']); + }); /** * Test for FAILURE @@ -4015,16 +4124,16 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $project1['headers']['status-code']); - \sleep(3); - // Ensure project 2 user is still there - $user2 = $this->client->call(Client::METHOD_GET, '/users/' . $user2['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2Id, - 'x-appwrite-key' => $key2['body']['secret'], - ]); + $this->assertEventually(function () use ($user2, $project2Id, $key2) { + $response = $this->client->call(Client::METHOD_GET, '/users/' . $user2['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2Id, + 'x-appwrite-key' => $key2['body']['secret'], + ]); - $this->assertEquals(200, $user2['headers']['status-code']); + $this->assertEquals(200, $response['headers']['status-code']); + }); // Create another user in project 2 in case read hits stale cache $user3 = $this->client->call(Client::METHOD_POST, '/users', [ From f0660f3fda356b3dacbd03d5766c12a261b4508a Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 16 Oct 2025 14:23:35 +0530 Subject: [PATCH 349/385] Replace sleep in webhook tests with assertEventually --- tests/e2e/Services/Webhooks/WebhooksBase.php | 70 ++++---- .../Webhooks/WebhooksCustomServerTest.php | 150 +++++++++--------- 2 files changed, 113 insertions(+), 107 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 41f6c03c35..3e542013bd 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -147,20 +147,22 @@ trait WebhooksBase $this->assertEquals($extra['body']['key'], 'extra'); // wait for database worker to kick in - sleep(10); + $this->assertEventually(function () use ($databaseId, $actorsId) { + $webhook = $this->getLastRequest(); + $this->assertNotEmpty($webhook); + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.attributes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + }, 15000, 500); $webhook = $this->getLastRequest(); $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.attributes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -498,20 +500,22 @@ trait WebhooksBase $this->assertEquals($extra['body']['key'], 'extra'); // wait for database worker to kick in - sleep(10); + $this->assertEventually(function () use ($databaseId, $actorsId) { + $webhook = $this->getLastRequest(); + $this->assertNotEmpty($webhook); + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.columns.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.columns.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + }, 15000, 500); $webhook = $this->getLastRequest(); $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.columns.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.columns.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -1487,17 +1491,17 @@ trait WebhooksBase $this->assertNotEmpty($newCollection['body']['$id']); } - sleep(10); + $this->assertEventually(function () use ($projectId, $webhookId) { + $webhook = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/webhooks/' . $webhookId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ])); - $webhook = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/webhooks/' . $webhookId, array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ])); - - // assert that the webhook is now disabled after 10 consecutive failures - $this->assertEquals($webhook['body']['enabled'], false); - $this->assertEquals($webhook['body']['attempts'], 10); + // assert that the webhook is now disabled after 10 consecutive failures + $this->assertEquals($webhook['body']['enabled'], false); + $this->assertEquals($webhook['body']['attempts'], 10); + }, 15000, 500); } } diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 1df6dfe9ae..d1f1106247 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Webhooks; +use Appwrite\Tests\Async; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -15,6 +16,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; class WebhooksCustomServerTest extends Scope { + use Async; use WebhooksBase; use ProjectCustom; use SideServer; @@ -89,24 +91,24 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals('fullname', $index['body']['key']); // wait for database worker to create index - sleep(5); + $this->assertEventually(function () use ($databaseId, $actorsId) { + $webhook = $this->getLastRequest(); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals('POST', $webhook['method']); - $this->assertEquals('application/json', $webhook['headers']['Content-Type']); - $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertTrue(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? '')); + $this->assertEquals('POST', $webhook['method']); + $this->assertEquals('application/json', $webhook['headers']['Content-Type']); + $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.collections.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertTrue(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? '')); + }, 10000, 500); // Remove index $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['actorsId'] . '/indexes/' . $index['body']['key'], array_merge([ @@ -275,24 +277,24 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals('fullname', $index['body']['key']); // wait for database worker to create index - sleep(5); + $this->assertEventually(function () use ($databaseId, $actorsId) { + $webhook = $this->getLastRequest(); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals('POST', $webhook['method']); - $this->assertEquals('application/json', $webhook['headers']['Content-Type']); - $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertTrue(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? '')); + $this->assertEquals('POST', $webhook['method']); + $this->assertEquals('application/json', $webhook['headers']['Content-Type']); + $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('databases.' . $databaseId . '.tables.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertTrue(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? '')); + }, 10000, 500); // Remove index $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $data['actorsId'] . '/indexes/' . $index['body']['key'], array_merge([ @@ -735,27 +737,27 @@ class WebhooksCustomServerTest extends Scope $this->assertNotEmpty($response['body']['$id']); // Wait for deployment to be built. - sleep(5); + $this->assertEventually(function () use ($deploymentId, $id) { + $webhook = $this->getLastRequest(); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals('POST', $webhook['method']); - $this->assertEquals('application/json', $webhook['headers']['Content-Type']); - $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); - // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals('POST', $webhook['method']); + $this->assertEquals('application/json', $webhook['headers']['Content-Type']); + $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + }, 10000, 500); /** * Test for FAILURE @@ -806,27 +808,27 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); // wait for timeout function to complete - sleep(20); + $this->assertEventually(function () use ($executionId, $id) { + $webhook = $this->getLastRequest(); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - - $this->assertEquals('POST', $webhook['method']); - $this->assertEquals('application/json', $webhook['headers']['Content-Type']); - $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); - // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals('POST', $webhook['method']); + $this->assertEquals('application/json', $webhook['headers']['Content-Type']); + $this->assertEquals('Appwrite-Server vdev. Please report abuse at security@appwrite.io', $webhook['headers']['User-Agent']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + }, 30000, 1000); /** * Test for FAILURE From 19d7f02371e9db9657437883011ba95d1ea27937 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 14:25:12 +0530 Subject: [PATCH 350/385] more tests --- .../Projects/ProjectsConsoleClientTest.php | 99 +++++++++++++++---- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index a7f57140cd..ff44207706 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -331,7 +331,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(3, $response['body']['total']); $this->assertIsArray($response['body']['projects']); $this->assertCount(3, $response['body']['projects']); - $this->assertEquals($response['body']['projects'][0]['$id'], $data['projectId']); + $this->assertEquals($response['body']['projects'][0]['$id'], $id); /** * Test pagination @@ -605,12 +605,33 @@ class ProjectsConsoleClientTest extends Scope return $data; } - /** - * @depends testCreateProject - */ - public function testUpdateProject($data): array + public function testUpdateProject(): void { - $id = $data['projectId'] ?? ''; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Update Project Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; /** * Test for SUCCESS @@ -644,17 +665,39 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(401, $response['headers']['status-code']); - - return ['projectId' => $projectId]; } /** * @group smtpAndTemplates - * @depends testCreateProject */ - public function testUpdateProjectSMTP($data): array + public function testUpdateProjectSMTP(): void { - $id = $data['projectId']; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Update Project SMTP Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -693,17 +736,39 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('user', $response['body']['smtpUsername']); $this->assertEquals('password', $response['body']['smtpPassword']); $this->assertEquals('', $response['body']['smtpSecure']); - - return $data; } /** * @group smtpAndTemplates - * @depends testCreateProject */ - public function testCreateProjectSMTPTests($data): array + public function testCreateProjectSMTPTests(): void { - $id = $data['projectId']; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Create Project SMTP Tests Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/smtp/tests', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -772,8 +837,6 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - - return $data; } /** From ae8ec1c7364afb2b67929ba4c5bc50f3376376c2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 14:26:39 +0530 Subject: [PATCH 351/385] formatting --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index ff44207706..2f760ba70f 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -534,7 +534,6 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/empty', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], From c6f144313de0a8e74cb6446cb318ce3de2f21f3c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 14:29:57 +0530 Subject: [PATCH 352/385] more tests --- .../Projects/ProjectsConsoleClientTest.php | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 2f760ba70f..9d05764f39 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -947,7 +947,6 @@ class ProjectsConsoleClientTest extends Scope /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/duration', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1173,18 +1172,39 @@ class ProjectsConsoleClientTest extends Scope $this->assertTrue($response['body']['authInvalidateSessions']); } - /** - * @depends testCreateProject - */ - public function testUpdateProjectOAuth($data): array + public function testUpdateProjectOAuth(): void { - $id = $data['projectId'] ?? ''; + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Update Project OAuth Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; + $providers = require(__DIR__ . '/../../../../app/config/oAuthProviders.php'); /** * Test for SUCCESS */ - foreach ($providers as $key => $provider) { $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/oauth2', array_merge([ 'content-type' => 'application/json', @@ -1269,7 +1289,6 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/oauth2', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1280,18 +1299,37 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - - return $data; } - /** - * @depends testCreateProject - */ - public function testUpdateProjectAuthStatus($data): array + public function testUpdateProjectAuthStatus(): void { - $id = $data['projectId'] ?? ''; - $auth = require(__DIR__ . '/../../../../app/config/auth.php'); + // Create a team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Update Project Auth Status Test Team', + ]); + $this->assertEquals(201, $team['headers']['status-code']); + $teamId = $team['body']['$id']; + + // Create a project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $id = $response['body']['$id']; + + $auth = require(__DIR__ . '/../../../../app/config/auth.php'); $originalEmail = uniqid() . 'user@localhost.test'; $originalPassword = 'password'; $originalName = 'User Name'; @@ -1425,8 +1463,6 @@ class ProjectsConsoleClientTest extends Scope 'status' => true, ]); } - - return $data; } /** From be983eb68ab8a3f97a7c015df009c3b5c3505ce4 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 16 Oct 2025 14:40:47 +0530 Subject: [PATCH 353/385] Remove race condition --- tests/e2e/Services/Webhooks/WebhooksBase.php | 28 +++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 3e542013bd..3d53a4a2ad 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -150,6 +150,7 @@ trait WebhooksBase $this->assertEventually(function () use ($databaseId, $actorsId) { $webhook = $this->getLastRequest(); $this->assertNotEmpty($webhook); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); @@ -159,16 +160,13 @@ trait WebhooksBase $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertStringContainsString("databases.{$databaseId}.collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertNotEmpty($webhook['data']['key']); + $this->assertEquals($webhook['data']['key'], 'extra'); }, 15000, 500); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertNotEmpty($webhook['data']['key']); - $this->assertEquals($webhook['data']['key'], 'extra'); - $removed = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['actorsId'] . '/attributes/' . $extra['body']['key'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -503,6 +501,7 @@ trait WebhooksBase $this->assertEventually(function () use ($databaseId, $actorsId) { $webhook = $this->getLastRequest(); $this->assertNotEmpty($webhook); + $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); @@ -512,16 +511,13 @@ trait WebhooksBase $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertStringContainsString("databases.{$databaseId}.tables.{$actorsId}.columns.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertNotEmpty($webhook['data']['key']); + $this->assertEquals($webhook['data']['key'], 'extra'); }, 15000, 500); - $webhook = $this->getLastRequest(); - $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertNotEmpty($webhook['data']['key']); - $this->assertEquals($webhook['data']['key'], 'extra'); - $removed = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $data['actorsId'] . '/columns/' . $extra['body']['key'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], From 83ec855f4bbef2744a3c32a4474beffbcfc05d75 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 15:18:21 +0530 Subject: [PATCH 354/385] skip harder ones --- .../Projects/ProjectsConsoleClientTest.php | 95 ++----------------- 1 file changed, 8 insertions(+), 87 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 9d05764f39..b8fdd71d3f 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -227,68 +227,11 @@ class ProjectsConsoleClientTest extends Scope /** * @group projectsCRUD + * @depends testCreateProject */ - public function testListProject(): void + public function testListProject($data): void { - // Create a team - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Project Test', - ]); - - $this->assertEquals(201, $team['headers']['status-code']); - - // Create first project - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Project Test', - 'teamId' => $team['body']['$id'], - 'region' => System::getEnv('_APP_REGION', 'default') - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $id = $response['body']['$id']; - - // Create second project - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Project Test', - 'teamId' => $team['body']['$id'], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Create a third project with different name - $team2 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Team 1', - ]); - - $this->assertEquals(201, $team2['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Team 1 Project', - 'teamId' => $team2['body']['$id'], - 'region' => System::getEnv('_APP_REGION', 'default') - ]); - - $this->assertEquals(201, $response['headers']['status-code']); + $id = $data['projectId']; /** * Test for SUCCESS @@ -668,35 +611,11 @@ class ProjectsConsoleClientTest extends Scope /** * @group smtpAndTemplates + * @depends testCreateProject */ - public function testUpdateProjectSMTP(): void + public function testUpdateProjectSMTP($data): array { - // Create a team - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Update Project SMTP Test Team', - ]); - - $this->assertEquals(201, $team['headers']['status-code']); - $teamId = $team['body']['$id']; - - // Create a project - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Project Test', - 'teamId' => $teamId, - 'region' => System::getEnv('_APP_REGION', 'default') - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $id = $response['body']['$id']; - + $id = $data['projectId']; $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -735,6 +654,8 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('user', $response['body']['smtpUsername']); $this->assertEquals('password', $response['body']['smtpPassword']); $this->assertEquals('', $response['body']['smtpSecure']); + + return $data; } /** From 5d1d937fde1f05578b1265c9de8f0d7153ae2b4f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 15:29:59 +0530 Subject: [PATCH 355/385] remove changes --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index b8fdd71d3f..91dce5c09c 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -229,9 +229,9 @@ class ProjectsConsoleClientTest extends Scope * @group projectsCRUD * @depends testCreateProject */ - public function testListProject($data): void + public function testListProject($data): array { - $id = $data['projectId']; + $id = $data['projectId'] ?? ''; /** * Test for SUCCESS @@ -274,7 +274,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(3, $response['body']['total']); $this->assertIsArray($response['body']['projects']); $this->assertCount(3, $response['body']['projects']); - $this->assertEquals($response['body']['projects'][0]['$id'], $id); + $this->assertEquals($response['body']['projects'][0]['$id'], $data['projectId']); /** * Test pagination @@ -432,6 +432,8 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); + + return $data; } public function testGetProject(): void From 33a856af416d3dda9782e628aa7bc8fd084934b8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 16 Oct 2025 15:54:43 +0530 Subject: [PATCH 356/385] update domain lib to 0.8.3 --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 6229720bf2..2d2d683603 100644 --- a/composer.lock +++ b/composer.lock @@ -756,16 +756,16 @@ }, { "name": "google/protobuf", - "version": "v4.32.1", + "version": "v4.33.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb" + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", - "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d", + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d", "shasum": "" }, "require": { @@ -794,9 +794,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0" }, - "time": "2025-09-14T05:14:52+00:00" + "time": "2025-10-15T20:10:28+00:00" }, { "name": "league/csv", @@ -3847,16 +3847,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "caa294dcebd05c8af876c8afef3e992faccdf645" + "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/caa294dcebd05c8af876c8afef3e992faccdf645", - "reference": "caa294dcebd05c8af876c8afef3e992faccdf645", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/b6d4c3a153fdd568d4b263e4ba299eed7b045a79", + "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79", "shasum": "" }, "require": { @@ -3902,9 +3902,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.2" + "source": "https://github.com/utopia-php/domains/tree/0.8.3" }, - "time": "2025-10-06T09:56:54+00:00" + "time": "2025-10-16T10:22:34+00:00" }, { "name": "utopia-php/dsn", From 1936a01464c3e66f86b88e2cb4dcdbbdf5019953 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 17 Oct 2025 12:28:31 +0530 Subject: [PATCH 357/385] chore: update apple and swift sdks to 13.2.2 --- app/config/platforms.php | 4 ++-- composer.lock | 24 ++++++++++++------------ docs/sdks/apple/CHANGELOG.md | 4 ++++ docs/sdks/swift/CHANGELOG.md | 4 ++++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 808ad486b7..2fcc296979 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '13.2.1', + 'version' => '13.2.2', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -418,7 +418,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '13.2.1', + 'version' => '13.2.2', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 6229720bf2..2d2d683603 100644 --- a/composer.lock +++ b/composer.lock @@ -756,16 +756,16 @@ }, { "name": "google/protobuf", - "version": "v4.32.1", + "version": "v4.33.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb" + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", - "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d", + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d", "shasum": "" }, "require": { @@ -794,9 +794,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0" }, - "time": "2025-09-14T05:14:52+00:00" + "time": "2025-10-15T20:10:28+00:00" }, { "name": "league/csv", @@ -3847,16 +3847,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "caa294dcebd05c8af876c8afef3e992faccdf645" + "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/caa294dcebd05c8af876c8afef3e992faccdf645", - "reference": "caa294dcebd05c8af876c8afef3e992faccdf645", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/b6d4c3a153fdd568d4b263e4ba299eed7b045a79", + "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79", "shasum": "" }, "require": { @@ -3902,9 +3902,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.2" + "source": "https://github.com/utopia-php/domains/tree/0.8.3" }, - "time": "2025-10-06T09:56:54+00:00" + "time": "2025-10-16T10:22:34+00:00" }, { "name": "utopia-php/dsn", diff --git a/docs/sdks/apple/CHANGELOG.md b/docs/sdks/apple/CHANGELOG.md index 6d67c4943f..762cd24b88 100644 --- a/docs/sdks/apple/CHANGELOG.md +++ b/docs/sdks/apple/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.2.2 + +* Fix issue: Missing AppwriteEnums dependency causing build failure + ## 13.2.1 * Add transaction support for Databases and TablesDB diff --git a/docs/sdks/swift/CHANGELOG.md b/docs/sdks/swift/CHANGELOG.md index 10119c524b..ed5f82c6ea 100644 --- a/docs/sdks/swift/CHANGELOG.md +++ b/docs/sdks/swift/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.2.2 + +* Fix issue: Missing AppwriteEnums dependency causing build failure + ## 13.2.1 * Add transaction support for Databases and TablesDB From abfab60eec35e8a95cfdc62c7d7e3082207fe527 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 17 Oct 2025 17:05:47 +0530 Subject: [PATCH 358/385] update to 0.8.4 --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 2d2d683603..1cf1b1e42b 100644 --- a/composer.lock +++ b/composer.lock @@ -3847,16 +3847,16 @@ }, { "name": "utopia-php/domains", - "version": "0.8.3", + "version": "0.8.4", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79" + "reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/b6d4c3a153fdd568d4b263e4ba299eed7b045a79", - "reference": "b6d4c3a153fdd568d4b263e4ba299eed7b045a79", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/316a76b0fcbc7f82e40be466c22c19acb243ee22", + "reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22", "shasum": "" }, "require": { @@ -3902,9 +3902,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.3" + "source": "https://github.com/utopia-php/domains/tree/0.8.4" }, - "time": "2025-10-16T10:22:34+00:00" + "time": "2025-10-17T11:31:56+00:00" }, { "name": "utopia-php/dsn", From 11042af051d0bd6abd89c8e49c3519977da7f4a1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 18 Oct 2025 02:19:43 +1300 Subject: [PATCH 359/385] Update db --- composer.json | 2 +- composer.lock | 249 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 240 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 04f34175ef..e6fa1e8556 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "2.*", + "utopia-php/database": "dev-feat-mongodb as 2.3.2", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index 1cf1b1e42b..d24a03a221 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": "9658991ad6520dad5807d7e1ed1e9bd4", + "content-hash": "a8e89f8ef7cb02bdbeae4dc0ad3c5bb0", "packages": [ { "name": "adhocore/jwt", @@ -959,6 +959,83 @@ }, "time": "2025-08-20T17:20:16+00:00" }, + { + "name": "mongodb/mongodb", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-mongodb": "^2.1", + "php": "^8.1", + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.32" + }, + "replace": { + "mongodb/builder": "*" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^1.2", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "6.5.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1" + }, + "time": "2025-08-13T20:50:05+00:00" + }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -3017,6 +3094,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -3690,29 +3847,31 @@ }, { "name": "utopia-php/database", - "version": "2.3.2", + "version": "dev-feat-mongodb", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "35c978ddd20b649d119296094686d3cc9fcf161f" + "reference": "b4e3d5501f7de6c849677d2cd1c923ea16562781" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/35c978ddd20b649d119296094686d3cc9fcf161f", - "reference": "35c978ddd20b649d119296094686d3cc9fcf161f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b4e3d5501f7de6c849677d2cd1c923ea16562781", + "reference": "b4e3d5501f7de6c849677d2cd1c923ea16562781", "shasum": "" }, "require": { "ext-mbstring": "*", + "ext-mongodb": "*", "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", + "utopia-php/mongo": "0.10.*", "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.*", + "laravel/pint": "*", "pcov/clobber": "2.*", "phpstan/phpstan": "1.*", "phpunit/phpunit": "9.*", @@ -3740,9 +3899,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.3.2" + "source": "https://github.com/utopia-php/database/tree/feat-mongodb" }, - "time": "2025-10-07T03:09:32+00:00" + "time": "2025-10-14T08:05:04+00:00" }, { "name": "utopia-php/detector", @@ -4296,6 +4455,67 @@ }, "time": "2025-09-10T05:45:30+00:00" }, + { + "name": "utopia-php/mongo", + "version": "0.10.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/mongo.git", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "shasum": "" + }, + "require": { + "ext-mongodb": "2.1.*", + "mongodb/mongodb": "2.1.*", + "php": ">=8.0", + "ramsey/uuid": "4.9.*" + }, + "require-dev": { + "fakerphp/faker": "1.*", + "laravel/pint": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "9.*", + "swoole/ide-helper": "5.1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Mongo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Wess", + "email": "wess@appwrite.io" + } + ], + "description": "A simple library to manage Mongo database", + "keywords": [ + "database", + "mongo", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/mongo/issues", + "source": "https://github.com/utopia-php/mongo/tree/0.10.0" + }, + "time": "2025-10-02T04:50:07+00:00" + }, { "name": "utopia-php/orchestration", "version": "0.9.1", @@ -8571,9 +8791,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-mongodb", + "alias": "2.3.2", + "alias_normalized": "2.3.2.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 248d3aea7ab52b86776e484a242f666141593b9d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 19 Oct 2025 05:44:44 +0000 Subject: [PATCH 360/385] Fix: reset argon 2 options to previous default --- app/config/collections/common.php | 5 ++--- app/controllers/api/users.php | 12 ++++++------ app/init/resources.php | 6 +++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/config/collections/common.php b/app/config/collections/common.php index 804929fcfd..eebc11e17f 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1,6 +1,5 @@ 256, 'signed' => true, 'required' => false, - 'default' => (new Argon2())->getName(), + 'default' => 'argon2', 'array' => false, 'filters' => [], ], @@ -184,7 +183,7 @@ return [ 'size' => 65535, 'signed' => true, 'required' => false, - 'default' => (new Argon2())->getOptions(), + 'default' => ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3], 'array' => false, 'filters' => ['json'], ], diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index eaa814efad..1dfa5c2603 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -351,9 +351,9 @@ App::post('/v1/users/argon2') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $argon2 = new Argon2(); $argon2 - ->setMemoryCost(7168) - ->setTimeCost(5) - ->setThreads(1); + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); $user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); @@ -1379,9 +1379,9 @@ App::patch('/v1/users/:userId/password') // Create Argon2 hasher with default settings $hasher = new Argon2(); $hasher - ->setMemoryCost(7168) - ->setTimeCost(5) - ->setThreads(1); + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); $newPassword = $hasher->hash($password); diff --git a/app/init/resources.php b/app/init/resources.php index 4087d102ca..48a6a102e3 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -983,9 +983,9 @@ App::setResource('store', function (): Store { App::setResource('proofForPassword', function (): Password { $hash = new Argon2(); $hash - ->setMemoryCost(7168) - ->setTimeCost(5) - ->setThreads(1); + ->setMemoryCost(2048) + ->setTimeCost(4) + ->setThreads(3); $password = new Password(); $password From 568651291e0d8ac9a363f41440a1ff35fc9dc810 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Oct 2025 23:13:17 +1300 Subject: [PATCH 361/385] Update libs --- composer.json | 2 +- composer.lock | 69 ++++++++++++++++++++++----------------------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index e6fa1e8556..df6fe95d3a 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "utopia-php/cache": "0.13.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-mongodb as 2.3.2", + "utopia-php/database": "3.*", "utopia-php/detector": "0.1.*", "utopia-php/domains": "0.8.*", "utopia-php/dns": "0.3.*", diff --git a/composer.lock b/composer.lock index d24a03a221..5649d0ed56 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": "a8e89f8ef7cb02bdbeae4dc0ad3c5bb0", + "content-hash": "3e8df036b4cb47d2eae34be382e04800", "packages": [ { "name": "adhocore/jwt", @@ -3450,16 +3450,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a" + "reference": "611fa66a97e87c0dbbc133a717d970da7a5ca828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a", - "reference": "cd591568791556d246d901d6aaf9935ab02c3f9a", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/611fa66a97e87c0dbbc133a717d970da7a5ca828", + "reference": "611fa66a97e87c0dbbc133a717d970da7a5ca828", "shasum": "" }, "require": { @@ -3467,7 +3467,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "2.*" + "utopia-php/database": "*" }, "require-dev": { "laravel/pint": "1.*", @@ -3495,9 +3495,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.0.1" + "source": "https://github.com/utopia-php/abuse/tree/1.0.2" }, - "time": "2025-09-04T12:46:54+00:00" + "time": "2025-10-20T07:18:33+00:00" }, { "name": "utopia-php/analytics", @@ -3547,21 +3547,21 @@ }, { "name": "utopia-php/audit", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22" + "reference": "8c17065c2473d4ca799f65585ca74eb53e1be211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", - "reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/8c17065c2473d4ca799f65585ca74eb53e1be211", + "reference": "8c17065c2473d4ca799f65585ca74eb53e1be211", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "2.*" + "utopia-php/database": "*" }, "require-dev": { "laravel/pint": "1.*", @@ -3588,9 +3588,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/1.0.1" + "source": "https://github.com/utopia-php/audit/tree/1.0.2" }, - "time": "2025-09-04T12:46:43+00:00" + "time": "2025-10-20T07:14:26+00:00" }, { "name": "utopia-php/auth", @@ -3847,16 +3847,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-mongodb", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b4e3d5501f7de6c849677d2cd1c923ea16562781" + "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b4e3d5501f7de6c849677d2cd1c923ea16562781", - "reference": "b4e3d5501f7de6c849677d2cd1c923ea16562781", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d41fd5649e01cc84be840dee95a7bf47eec891a5", + "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5", "shasum": "" }, "require": { @@ -3899,9 +3899,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-mongodb" + "source": "https://github.com/utopia-php/database/tree/3.0.0" }, - "time": "2025-10-14T08:05:04+00:00" + "time": "2025-10-20T05:51:31+00:00" }, { "name": "utopia-php/detector", @@ -4401,16 +4401,16 @@ }, { "name": "utopia-php/migration", - "version": "1.1.0", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038" + "reference": "e0b6687620dd67fe2b96eb4419e8243577b11c8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/6fb6f8f032cd34c3c65728a55d494adeac2ff038", - "reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/e0b6687620dd67fe2b96eb4419e8243577b11c8f", + "reference": "e0b6687620dd67fe2b96eb4419e8243577b11c8f", "shasum": "" }, "require": { @@ -4418,7 +4418,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "2.*", + "utopia-php/database": "3.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4451,9 +4451,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.1.0" + "source": "https://github.com/utopia-php/migration/tree/1.2.2" }, - "time": "2025-09-10T05:45:30+00:00" + "time": "2025-10-20T10:12:11+00:00" }, { "name": "utopia-php/mongo", @@ -8791,18 +8791,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-mongodb", - "alias": "2.3.2", - "alias_normalized": "2.3.2.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From bfe639fb70beb0fa8060f03ce360b6b791f37be0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 00:13:32 +1300 Subject: [PATCH 362/385] Fix key --- .../Databases/Http/Databases/Collections/Indexes/Create.php | 2 +- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php index 0c6ef8bb23..6772b6f75e 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php @@ -171,7 +171,7 @@ class Create extends Action } if ($attributeArray === true) { - $lengths[$i] = Database::ARRAY_INDEX_LENGTH; + $lengths[$i] = Database::MAX_ARRAY_INDEX_LENGTH; $orders[$i] = null; } } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index bfc56567ef..ff9c4357b2 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1465,7 +1465,7 @@ trait DatabasesBase $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 + // set a length for an array attribute, it should get overriden with Database::MAX_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'], @@ -1483,7 +1483,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]); - $this->assertEquals([Database::ARRAY_INDEX_LENGTH], $index['body']['lengths']); + $this->assertEquals([Database::MAX_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", [ From d23f993a6c7e186b44ca11eded3588b5211cec13 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 00:13:43 +1300 Subject: [PATCH 363/385] Fix validator constructor --- .../Databases/Collections/Indexes/Create.php | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php index 6772b6f75e..9fb438d577 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php @@ -160,7 +160,7 @@ class Create extends Action throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']); } - // ensure attribute is available + // Ensure attribute is available if ($attributeStatus !== 'available') { $contextType = ucfirst($contextType); throw new Exception($this->getParentNotAvailableException(), "$contextType not available: " . $oldAttributes[$attributeIndex]['key']); @@ -190,21 +190,18 @@ class Create extends Action 'orders' => $orders, ]); - $maxIndexLength = $dbForProject->getAdapter()->getMaxIndexLength(); - $internalIndexesKeys = $dbForProject->getAdapter()->getInternalIndexesKeys(); - $supportForIndexArray = $dbForProject->getAdapter()->getSupportForIndexArray(); - $supportForSpatialAttributes = $dbForProject->getAdapter()->getSupportForSpatialAttributes(); - $supportForSpatialIndexNull = $dbForProject->getAdapter()->getSupportForSpatialIndexNull(); - $supportForSpatialIndexOrder = $dbForProject->getAdapter()->getSupportForSpatialIndexOrder(); - $validator = new IndexValidator( $collection->getAttribute('attributes'), - $maxIndexLength, - $internalIndexesKeys, - $supportForIndexArray, - $supportForSpatialAttributes, - $supportForSpatialIndexNull, - $supportForSpatialIndexOrder + $collection->getAttribute('indexes'), + $dbForProject->getAdapter()->getMaxIndexLength(), + $dbForProject->getAdapter()->getInternalIndexesKeys(), + $dbForProject->getAdapter()->getSupportForIndexArray(), + $dbForProject->getAdapter()->getSupportForSpatialIndexNull(), + $dbForProject->getAdapter()->getSupportForSpatialIndexOrder(), + $dbForProject->getAdapter()->getSupportForVectors(), + $dbForProject->getAdapter()->getSupportForAttributes(), + $dbForProject->getAdapter()->getSupportForMultipleFulltextIndexes(), + $dbForProject->getAdapter()->getSupportForIdenticalIndexes() ); if (!$validator->isValid($index)) { From 94d02aff4fe427540afedd39132dc6e1135aa371 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 01:24:50 +1300 Subject: [PATCH 364/385] Fix DB tests --- tests/e2e/Services/Databases/Legacy/DatabasesBase.php | 2 +- tests/e2e/Services/Databases/TablesDB/DatabasesBase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index ff9c4357b2..8827637427 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1281,7 +1281,7 @@ trait DatabasesBase ]); $this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']); - $this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a FULLTEXT index, must be of type string'); + $this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a fulltext index, must be of type string'); $noAttributes = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index d3aa50a99a..5c371e814e 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -1281,7 +1281,7 @@ trait DatabasesBase ]); $this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']); - $this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a FULLTEXT index, must be of type string'); + $this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a fulltext index, must be of type string'); $noAttributes = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', From 46dd8fdc68e9d4a2c4c863821887079bd6191351 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:28:04 +0000 Subject: [PATCH 365/385] Initial plan From d9773f44b1ffc556f4cfc04899f11de0754d8eaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:32:38 +0000 Subject: [PATCH 366/385] Change GitHub alert from NOTE to TIP in VCS comments Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com> --- src/Appwrite/Vcs/Comment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Vcs/Comment.php b/src/Appwrite/Vcs/Comment.php index a66706a4a2..2a08b701f0 100644 --- a/src/Appwrite/Vcs/Comment.php +++ b/src/Appwrite/Vcs/Comment.php @@ -193,7 +193,7 @@ class Comment } $tip = $this->tips[array_rand($this->tips)]; - $text .= "\n
\n\n> [!NOTE]\n> $tip\n\n"; + $text .= "\n
\n\n> [!TIP]\n> $tip\n\n"; return $text; } From caa699e181d9f419856f5d6c11aeb2edea14abab Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 02:25:51 +1300 Subject: [PATCH 367/385] Update database --- composer.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.lock b/composer.lock index 5649d0ed56..f65e715647 100644 --- a/composer.lock +++ b/composer.lock @@ -3847,16 +3847,16 @@ }, { "name": "utopia-php/database", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5" + "reference": "da0d583e1590e37515edfa338d8684c01833455f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d41fd5649e01cc84be840dee95a7bf47eec891a5", - "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/da0d583e1590e37515edfa338d8684c01833455f", + "reference": "da0d583e1590e37515edfa338d8684c01833455f", "shasum": "" }, "require": { @@ -5154,28 +5154,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "541057574806f942c94662b817a50f63f7345360" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/541057574806f942c94662b817a50f63f7345360", + "reference": "541057574806f942c94662b817a50f63f7345360", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -5206,9 +5206,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.0" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-20T12:43:39+00:00" }, { "name": "webonyx/graphql-php", From ddde13a78fd621f9fee091f87d3f48df4352ad5a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 14:59:30 +1300 Subject: [PATCH 368/385] Revert "Merge pull request #10468 from appwrite/feat-apps-module-dl" This reverts commit 9dd1939d1fb1fe81c0d7ab009bc2d43418fad3d2, reversing changes made to 8dfdfcb522d3dcabbfdb5e1d576b4c645b8a1619. # Conflicts: # app/config/collections/common.php # app/controllers/api/users.php # app/init/resources.php # composer.lock --- app/config/collections/common.php | 7 +- app/config/console.php | 3 +- app/config/roles.php | 13 +- app/controllers/api/account.php | 441 +++++++----------- app/controllers/api/projects.php | 3 +- app/controllers/api/teams.php | 56 +-- app/controllers/api/users.php | 144 ++---- app/controllers/general.php | 28 +- app/controllers/shared/api.php | 79 +--- app/controllers/shared/api/auth.php | 2 +- app/init/constants.php | 66 --- app/init/resources.php | 113 ++--- app/realtime.php | 22 +- composer.json | 1 - composer.lock | 57 +-- src/Appwrite/Auth/Auth.php | 351 ++++++++++++-- src/Appwrite/Auth/Hash.php | 62 +++ src/Appwrite/Auth/Hash/Argon2.php | 47 ++ src/Appwrite/Auth/Hash/Bcrypt.php | 46 ++ src/Appwrite/Auth/Hash/Md5.php | 44 ++ src/Appwrite/Auth/Hash/Phpass.php | 290 ++++++++++++ src/Appwrite/Auth/Hash/Scrypt.php | 51 ++ src/Appwrite/Auth/Hash/Scryptmodified.php | 80 ++++ src/Appwrite/Auth/Hash/Sha.php | 50 ++ src/Appwrite/Auth/Key.php | 8 +- src/Appwrite/Auth/MFA/Type.php | 5 +- .../Auth/Validator/PasswordHistory.php | 12 +- src/Appwrite/Migration/Version/V16.php | 3 +- src/Appwrite/Migration/Version/V17.php | 4 +- src/Appwrite/Migration/Version/V20.php | 11 +- .../Functions/Http/Executions/Create.php | 8 +- src/Appwrite/Platform/Tasks/Install.php | 9 +- src/Appwrite/Platform/Workers/Audits.php | 3 +- src/Appwrite/Platform/Workers/Deletes.php | 3 +- .../Utopia/Response/Model/Project.php | 4 +- tests/e2e/Scopes/ProjectCustom.php | 1 - .../Account/AccountConsoleClientTest.php | 2 +- .../Projects/ProjectsConsoleClientTest.php | 7 +- tests/unit/Auth/AuthTest.php | 268 +++++++++-- tests/unit/Auth/KeyTest.php | 5 +- .../unit/Messaging/MessagingChannelsTest.php | 4 +- tests/unit/Migration/MigrationTest.php | 2 +- 42 files changed, 1560 insertions(+), 855 deletions(-) create mode 100644 src/Appwrite/Auth/Hash.php create mode 100644 src/Appwrite/Auth/Hash/Argon2.php create mode 100644 src/Appwrite/Auth/Hash/Bcrypt.php create mode 100644 src/Appwrite/Auth/Hash/Md5.php create mode 100644 src/Appwrite/Auth/Hash/Phpass.php create mode 100644 src/Appwrite/Auth/Hash/Scrypt.php create mode 100644 src/Appwrite/Auth/Hash/Scryptmodified.php create mode 100644 src/Appwrite/Auth/Hash/Sha.php diff --git a/app/config/collections/common.php b/app/config/collections/common.php index eebc11e17f..6de7eb224b 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1,5 +1,6 @@ 256, 'signed' => true, 'required' => false, - 'default' => 'argon2', + 'default' => Auth::DEFAULT_ALGO, 'array' => false, 'filters' => [], ], @@ -183,7 +184,7 @@ return [ 'size' => 65535, 'signed' => true, 'required' => false, - 'default' => ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3], + 'default' => Auth::DEFAULT_ALGO_OPTIONS, 'array' => false, 'filters' => ['json'], ], @@ -1114,9 +1115,9 @@ return [ [ '$id' => ID::custom('expire'), 'type' => Database::VAR_DATETIME, + 'format' => '', 'size' => 0, 'required' => false, - 'format' => '', 'signed' => false, 'default' => null, 'array' => false, diff --git a/app/config/console.php b/app/config/console.php index 5c4bf87614..f8f68a8039 100644 --- a/app/config/console.php +++ b/app/config/console.php @@ -4,6 +4,7 @@ * Initializes console project document. */ +use Appwrite\Auth\Auth; use Appwrite\Network\Platform; use Utopia\Database\Helpers\ID; use Utopia\System\System; @@ -37,7 +38,7 @@ $console = [ 'mockNumbers' => [], 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user - 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled', 'invalidateSessions' => true ], diff --git a/app/config/roles.php b/app/config/roles.php index 966d24663f..0f0945a2b4 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -1,5 +1,6 @@ [ + Auth::USER_ROLE_GUESTS => [ 'label' => 'Guests', 'scopes' => [ 'global', @@ -111,23 +112,23 @@ return [ 'execution.write', ], ], - USER_ROLE_USERS => [ + Auth::USER_ROLE_USERS => [ 'label' => 'Users', 'scopes' => \array_merge($member), ], - USER_ROLE_ADMIN => [ + Auth::USER_ROLE_ADMIN => [ 'label' => 'Admin', 'scopes' => \array_merge($admins), ], - USER_ROLE_DEVELOPER => [ + Auth::USER_ROLE_DEVELOPER => [ 'label' => 'Developer', 'scopes' => \array_merge($admins), ], - USER_ROLE_OWNER => [ + Auth::USER_ROLE_OWNER => [ 'label' => 'Owner', 'scopes' => \array_merge($member, $admins), ], - USER_ROLE_APPS => [ + Auth::USER_ROLE_APPS => [ 'label' => 'Applications', 'scopes' => ['global', 'health.read', 'graphql'], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 418770fc9c..5563fc6a59 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -40,11 +40,6 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; -use Utopia\Auth\Hashes\Sha; -use Utopia\Auth\Proofs\Code as ProofsCode; -use Utopia\Auth\Proofs\Password as ProofsPassword; -use Utopia\Auth\Proofs\Token as ProofsToken; -use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -174,7 +169,8 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc } ; -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) { + +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { /** @var Utopia\Database\Document $user */ $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -183,8 +179,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res throw new Exception(Exception::USER_INVALID_TOKEN); } - $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForToken) - ?: Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret, $proofForCode); + $verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -192,17 +187,17 @@ $createSession = function (string $userId, string $secret, Request $request, Res $user->setAttributes($userFromRequest->getArrayCopy()); - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $sessionSecret = $proofForToken->generate(); + $sessionSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); $factor = (match ($verifiedToken->getAttribute('type')) { - TOKEN_TYPE_MAGIC_URL, - TOKEN_TYPE_OAUTH2, - TOKEN_TYPE_EMAIL => Type::EMAIL, - TOKEN_TYPE_PHONE => Type::PHONE, - TOKEN_TYPE_GENERIC => 'token', + Auth::TOKEN_TYPE_MAGIC_URL, + Auth::TOKEN_TYPE_OAUTH2, + Auth::TOKEN_TYPE_EMAIL => Type::EMAIL, + Auth::TOKEN_TYPE_PHONE => Type::PHONE, + Auth::TOKEN_TYPE_GENERIC => 'token', default => throw new Exception(Exception::USER_INVALID_TOKEN) }); @@ -212,7 +207,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'provider' => Auth::getSessionProviderByTokenType($verifiedToken->getAttribute('type')), - 'secret' => $proofForToken->hash($sessionSecret), // One way hash encryption to protect DB leak + 'secret' => Auth::hash($sessionSecret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => [$factor], @@ -237,11 +232,11 @@ $createSession = function (string $userId, string $secret, Request $request, Res $dbForProject->purgeCachedDocument('users', $user->getId()); // Magic URL + Email OTP - if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_MAGIC_URL || $verifiedToken->getAttribute('type') === TOKEN_TYPE_EMAIL) { + if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_MAGIC_URL || $verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_EMAIL) { $user->setAttribute('emailVerification', true); } - if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) { + if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_PHONE) { $user->setAttribute('phoneVerification', true); } @@ -252,8 +247,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res } $isAllowedTokenType = match ($verifiedToken->getAttribute('type')) { - TOKEN_TYPE_MAGIC_URL, - TOKEN_TYPE_EMAIL => false, + Auth::TOKEN_TYPE_MAGIC_URL, + Auth::TOKEN_TYPE_EMAIL => false, default => true }; @@ -273,21 +268,16 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setParam('userId', $user->getId()) ->setParam('sessionId', $session->getId()); - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $sessionSecret) - ->encode(); - if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $sessionSecret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $protocol = $request->getProtocol(); $response - ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -296,7 +286,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setAttribute('current', true) ->setAttribute('countryName', $countryName) ->setAttribute('expire', $expire) - ->setAttribute('secret', $encoded) + ->setAttribute('secret', Auth::encodeSession($user->getId(), $sessionSecret)) ; $response->dynamic($session, Response::MODEL_SESSION); @@ -382,9 +372,7 @@ App::post('/v1/account') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $proof = new ProofsPassword(); - $hash = $proof->hash($password); - + $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); try { $userId = $userId == 'unique()' ? ID::unique() : $userId; $user->setAttributes([ @@ -397,11 +385,11 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'password' => $hash, - 'passwordHistory' => $passwordHistory > 0 ? [$hash] : [], + 'password' => $password, + 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], 'passwordUpdate' => DateTime::now(), - 'hash' => $proof->getHash()->getName(), - 'hashOptions' => $proof->getHash()->getOptions(), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -556,13 +544,12 @@ App::get('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('locale') - ->inject('store') - ->inject('proofForToken') - ->action(function (Response $response, Document $user, Locale $locale, Store $store, ProofsToken $proofForToken) { + ->inject('project') + ->action(function (Response $response, Document $user, Locale $locale, Document $project) { $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); + $current = Auth::sessionVerify($sessions, Auth::$secret); foreach ($sessions as $key => $session) {/** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -609,9 +596,7 @@ App::delete('/v1/account/sessions') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('store') - ->inject('proofForToken') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes) { $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); @@ -627,13 +612,13 @@ App::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { $session->setAttribute('current', true); // If current session delete the cookies too $response - ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); // Use current session for events. $queueForEvents @@ -677,13 +662,12 @@ App::get('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('locale') - ->inject('store') - ->inject('proofForToken') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Store $store, ProofsToken $proofForToken) { + ->inject('project') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) { $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ @@ -691,7 +675,7 @@ App::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret')))) + ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', $session->getAttribute('secret', '')) ; @@ -734,13 +718,12 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('store') - ->inject('proofForToken') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) { + ->inject('project') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Document $project) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -757,7 +740,7 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); @@ -767,8 +750,8 @@ App::delete('/v1/account/sessions/:sessionId') } $response - ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } $dbForProject->purgeCachedDocument('users', $user->getId()); @@ -819,12 +802,10 @@ App::patch('/v1/account/sessions/:sessionId') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForToken') - ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store, ProofsToken $proofForToken) { + ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) { $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -841,7 +822,7 @@ App::patch('/v1/account/sessions/:sessionId') } // Extend session - $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session->setAttribute('expire', DateTime::addSeconds(new \DateTime(), $authDuration)); // Refresh OAuth access token @@ -913,10 +894,7 @@ App::post('/v1/account/sessions/email') ->inject('queueForEvents') ->inject('queueForMails') ->inject('hooks') - ->inject('store') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -924,9 +902,7 @@ App::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - $userProofForPassword = ProofsPassword::createHash($profile->getAttribute('hash', $proofForPassword->getHash()->getName()), $profile->getAttribute('hashOptions', $proofForPassword->getHash()->getOptions())); - - if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !$userProofForPassword->verify($password, $profile->getAttribute('password'))) { + if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -938,18 +914,18 @@ App::post('/v1/account/sessions/email') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); $session = new Document(array_merge( [ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $email, - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => ['password'], @@ -964,12 +940,11 @@ App::post('/v1/account/sessions/email') Authorization::setRole(Role::user($user->getId())->toString()); // Re-hash if not using recommended algo - if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) { - $proofForPasswordUpdated = new ProofsPassword(); + if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { $user - ->setAttribute('password', $proofForPasswordUpdated->hash($password)) - ->setAttribute('hash', $proofForPasswordUpdated->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPasswordUpdated->getHash()->getOptions()); + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $dbForProject->updateDocument('users', $user->getId(), $user); } @@ -981,20 +956,17 @@ App::post('/v1/account/sessions/email') Permission::delete(Role::user($user->getId())), ])); - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); + $response + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) + ; } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1003,7 +975,7 @@ App::post('/v1/account/sessions/email') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) - ->setAttribute('secret', $encoded) + ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ; $queueForEvents @@ -1057,10 +1029,7 @@ App::post('/v1/account/sessions/anonymous') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) { $protocol = $request->getProtocol(); if ('console' === $project->getId()) { @@ -1089,8 +1058,8 @@ App::post('/v1/account/sessions/anonymous') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -1108,18 +1077,18 @@ App::post('/v1/account/sessions/anonymous') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); // Create session token - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); $session = new Document(array_merge( [ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'provider' => SESSION_PROVIDER_ANONYMOUS, - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => ['anonymous'], @@ -1146,20 +1115,15 @@ App::post('/v1/account/sessions/anonymous') ->setParam('sessionId', $session->getId()) ; - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1168,7 +1132,7 @@ App::post('/v1/account/sessions/anonymous') $session ->setAttribute('current', true) ->setAttribute('countryName', $countryName) - ->setAttribute('secret', $encoded) + ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ; $response->dynamic($session, Response::MODEL_SESSION); @@ -1209,9 +1173,6 @@ App::post('/v1/account/sessions/token') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('store') - ->inject('proofForToken') - ->inject('proofForCode') ->action($createSession); App::get('/v1/account/sessions/oauth2/:provider') @@ -1404,10 +1365,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) { + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) { $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; $port = $request->getPort(); $callbackBase = $protocol . '://' . $request->getHostname(); @@ -1551,7 +1509,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); + $current = Auth::sessionVerify($sessions, Auth::$secret); if ($current) { // Delete current session of new one. $currentDocument = $dbForProject->getDocument('sessions', $current); @@ -1632,8 +1590,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => true, // Email should already be authenticated by OAuth2 provider 'password' => null, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -1732,19 +1690,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); - $proofsForTokenOAuth2 = new ProofsToken(TOKEN_LENGTH_OAUTH2); // If the `token` param is set, we will return the token in the query string if ($state['token']) { - $secret = $proofsForTokenOAuth2->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_OAUTH2); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_OAUTH2, - 'secret' => $proofsForTokenOAuth2->hash($secret), // One way hash encryption to protect DB leak + 'type' => Auth::TOKEN_TYPE_OAUTH2, + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -1772,7 +1729,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } else { $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); $session = new Document(array_merge([ '$id' => ID::unique(), @@ -1782,8 +1739,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerUid' => $oauth2ID, 'providerAccessToken' => $accessToken, 'providerRefreshToken' => $refreshToken, - 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry), - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry), + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks @@ -1799,13 +1756,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $session->setAttribute('expire', $expire); - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $queueForEvents @@ -1818,13 +1770,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') if ($state['success']['path'] == $oauthDefaultSuccess) { $query['project'] = $project->getId(); $query['domain'] = Config::getParam('cookieDomain'); - $query['key'] = $store->getKey(); - $query['secret'] = $encoded; + $query['key'] = Auth::$cookieName; + $query['secret'] = Auth::encodeSession($user->getId(), $secret); } $response - ->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } if (isset($sessionUpgrade) && $sessionUpgrade) { @@ -1982,8 +1934,7 @@ App::post('/v1/account/tokens/magic-url') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('proofForPassword') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword) { + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2029,8 +1980,8 @@ App::post('/v1/account/tokens/magic-url') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -2048,18 +1999,15 @@ App::post('/v1/account/tokens/magic-url') Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } - $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); - $proofForToken->setHash(new Sha()); - - $tokenSecret = $proofForToken->generate(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); + $tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_MAGIC_URL, - 'secret' => $proofForToken->hash($tokenSecret), // One way hash encryption to protect DB leak + 'type' => Auth::TOKEN_TYPE_MAGIC_URL, + 'secret' => Auth::hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2238,9 +2186,7 @@ App::post('/v1/account/tokens/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('proofForPassword') - ->inject('proofForCode') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) { + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2284,8 +2230,8 @@ App::post('/v1/account/tokens/email') 'emailVerification' => false, 'status' => true, 'password' => null, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => null, 'registration' => DateTime::now(), 'reset' => false, @@ -2324,15 +2270,15 @@ App::post('/v1/account/tokens/email') $dbForProject->purgeCachedDocument('users', $user->getId()); } - $tokenSecret = $proofForCode->generate(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); + $tokenSecret = Auth::codeGenerator(6); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_EMAIL, - 'secret' => $proofForCode->hash($tokenSecret), // One way hash encryption to protect DB leak + 'type' => Auth::TOKEN_TYPE_EMAIL, + 'secret' => Auth::hash($tokenSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2519,13 +2465,7 @@ App::put('/v1/account/sessions/magic-url') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('store') - ->inject('proofForCode') - ->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) { - $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); - $proofForToken->setHash(new Sha()); - $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode); - }); + ->action($createSession); App::put('/v1/account/sessions/phone') ->desc('Update phone session') @@ -2566,9 +2506,6 @@ App::put('/v1/account/sessions/phone') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('store') - ->inject('proofForToken') - ->inject('proofForCode') ->action($createSession); App::post('/v1/account/tokens/phone') @@ -2609,9 +2546,7 @@ App::post('/v1/account/tokens/phone') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->inject('store') - ->inject('proofForCode') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) { + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -2690,15 +2625,15 @@ App::post('/v1/account/tokens/phone') } } - $secret ??= $proofForCode->generate(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_OTP)); + $secret ??= Auth::codeGenerator(); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_PHONE, - 'secret' => $proofForCode->hash($secret), + 'type' => Auth::TOKEN_TYPE_PHONE, + 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -2773,11 +2708,7 @@ App::post('/v1/account/tokens/phone') ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); // Encode secret for clients - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - $token->setAttribute('secret', $encoded); + $token->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -2808,16 +2739,20 @@ App::post('/v1/account/jwts') ->label('abuse-key', 'url:{url},userId:{userId}') ->inject('response') ->inject('user') - ->inject('store') - ->inject('proofForToken') - ->action(function (Response $response, Document $user, Store $store, ProofsToken $proofForToken) { + ->inject('dbForProject') + ->action(function (Response $response, Document $user, Database $dbForProject) { $sessions = $user->getAttribute('sessions', []); + $current = new Document(); - $sessionId = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); + foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $current = $session; + } + } - if (!$sessionId) { + if ($current->isEmpty()) { throw new Exception(Exception::USER_SESSION_NOT_FOUND); } @@ -2828,7 +2763,7 @@ App::post('/v1/account/jwts') ->dynamic(new Document([ 'jwt' => $jwt->encode([ 'userId' => $user->getId(), - 'sessionId' => $sessionId, + 'sessionId' => $current->getId(), ]) ]), Response::MODEL_JWT); }); @@ -3004,23 +2939,18 @@ App::patch('/v1/account/password') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->inject('store') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { - $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); + ->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + // Check old password only if its an existing user. - if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password + if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - $newPassword = $proofForPassword->hash($password); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); $history = $user->getAttribute('passwordHistory', []); - if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $hash); + $validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions')); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } @@ -3042,13 +2972,11 @@ App::patch('/v1/account/password') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', $proofForPassword->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()); + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $sessions = $user->getAttribute('sessions', []); - - $current = Auth::sessionVerify($sessions, $store->getProperty('secret', ''), $proofForToken); - + $current = Auth::sessionVerify($sessions, Auth::$secret); $invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false; if ($invalidate && !empty($current)) { foreach ($sessions as $session) { @@ -3096,16 +3024,13 @@ App::patch('/v1/account/email') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->inject('proofForPassword') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); - $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); - if ( !empty($passwordUpdate) && - !$userProofForPassword->verify($password, $user->getAttribute('password')) + !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -3132,9 +3057,9 @@ App::patch('/v1/account/email') if (empty($passwordUpdate)) { $user - ->setAttribute('password', $proofForPassword->hash($password)) - ->setAttribute('hash', $proofForPassword->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) ->setAttribute('passwordUpdate', DateTime::now()); } @@ -3196,16 +3121,13 @@ App::patch('/v1/account/phone') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->inject('proofForPassword') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); - $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); - if ( !empty($passwordUpdate) && - !$userProofForPassword->verify($password, $user->getAttribute('password')) + !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -3229,9 +3151,9 @@ App::patch('/v1/account/phone') if (empty($passwordUpdate)) { $user - ->setAttribute('password', $proofForPassword->hash($password)) - ->setAttribute('hash', $proofForPassword->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) ->setAttribute('passwordUpdate', DateTime::now()); } @@ -3320,8 +3242,7 @@ App::patch('/v1/account/status') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('store') - ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Store $store) { + ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { $user->setAttribute('status', false); @@ -3337,8 +3258,8 @@ App::patch('/v1/account/status') $protocol = $request->getProtocol(); $response - ->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic($user, Response::MODEL_ACCOUNT); @@ -3378,8 +3299,7 @@ App::post('/v1/account/recovery') ->inject('locale') ->inject('queueForMails') ->inject('queueForEvents') - ->inject('proofForToken') - ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) { + ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); @@ -3401,15 +3321,15 @@ App::post('/v1/account/recovery') throw new Exception(Exception::USER_BLOCKED); } - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_RECOVERY)); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_RECOVERY)); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_RECOVERY); $recovery = new Document([ '$id' => ID::unique(), 'userId' => $profile->getId(), 'userInternalId' => $profile->getSequence(), - 'type' => TOKEN_TYPE_RECOVERY, - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -3557,9 +3477,7 @@ App::put('/v1/account/recovery') ->inject('project') ->inject('queueForEvents') ->inject('hooks') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3567,7 +3485,7 @@ App::put('/v1/account/recovery') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_RECOVERY, $secret, $proofForToken); + $verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3575,14 +3493,12 @@ App::put('/v1/account/recovery') Authorization::setRole(Role::user($profile->getId())->toString()); - $newPassword = $proofForPassword->hash($password); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $hash = ProofsPassword::createHash($profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $profile->getAttribute('passwordHistory', []); - if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $hash); + $validator = new PasswordHistory($history, $profile->getAttribute('hash'), $profile->getAttribute('hashOptions')); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } @@ -3594,12 +3510,12 @@ App::put('/v1/account/recovery') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile - ->setAttribute('password', $newPassword) - ->setAttribute('passwordHistory', $history) - ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', $proofForPassword->getHash()->getName()) - ->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions()) - ->setAttribute('emailVerification', true)); + ->setAttribute('password', $newPassword) + ->setAttribute('passwordHistory', $history) + ->setAttribute('passwordUpdate', DateTime::now()) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('emailVerification', true)); $user->setAttributes($profile->getArrayCopy()); @@ -3673,8 +3589,7 @@ App::post('/v1/account/verifications/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('proofForToken') - ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) { + ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); @@ -3689,15 +3604,15 @@ App::post('/v1/account/verifications/email') throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } - $verificationSecret = $proofForToken->generate(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); + $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_VERIFICATION, - 'secret' => $proofForToken->hash($verificationSecret), // One way hash encryption to protect DB leak + 'type' => Auth::TOKEN_TYPE_VERIFICATION, + 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -3886,8 +3801,7 @@ App::put('/v1/account/verifications/email') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('proofForToken') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) { + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -3896,7 +3810,7 @@ App::put('/v1/account/verifications/email') } $tokens = $profile->getAttribute('tokens', []); - $verifiedToken = Auth::tokenVerify($tokens, TOKEN_TYPE_VERIFICATION, $secret, $proofForToken); + $verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -3961,8 +3875,7 @@ App::post('/v1/account/verifications/phone') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->inject('proofForCode') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3987,15 +3900,15 @@ App::post('/v1/account/verifications/phone') } } - $secret ??= $proofForCode->generate(); - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); + $secret ??= Auth::codeGenerator(); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); $verification = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_PHONE, - 'secret' => $proofForCode->hash($secret), + 'type' => Auth::TOKEN_TYPE_PHONE, + 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), @@ -4106,8 +4019,7 @@ App::put('/v1/account/verifications/phone') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('proofForCode') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) { + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); @@ -4115,7 +4027,7 @@ App::put('/v1/account/verifications/phone') throw new Exception(Exception::USER_NOT_FOUND); } - $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), TOKEN_TYPE_PHONE, $secret, $proofForCode); + $verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_PHONE, $secret); if (!$verifiedToken) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -4756,18 +4668,15 @@ App::post('/v1/account/mfa/challenge') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->inject('proofForToken') - ->inject('proofForCode') - ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsToken $proofForToken, ProofsCode $proofForCode) { + ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), TOKEN_EXPIRATION_CONFIRM)); - - $code = $proofForCode->generate(); + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); + $code = Auth::codeGenerator(); $challenge = new Document([ 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), 'type' => $factor, - 'token' => $proofForToken->generate(), + 'token' => Auth::tokenGenerator(), 'code' => $code, 'expire' => $expire, '$permissions' => [ @@ -5112,9 +5021,7 @@ App::post('/v1/account/targets/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('store') - ->inject('proofForToken') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) { + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); @@ -5130,7 +5037,7 @@ App::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret); $session = $dbForProject->getDocument('sessions', $sessionId); try { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 1d68377d8c..80d407322e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,6 +1,7 @@ APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, - 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false, 'mockNumbers' => [], 'sessionAlerts' => false, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 387fb7d48b..7398e451b5 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -28,9 +28,6 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit; -use Utopia\Auth\Proofs\Password; -use Utopia\Auth\Proofs\Token; -use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -474,9 +471,10 @@ App::post('/v1/teams/:teamId/memberships') ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('roles', [], function (Document $project) { if ($project->getId() === 'console') { + ; $roles = array_keys(Config::getParam('roles', [])); - $roles = array_filter($roles, function ($role) { - return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]); + array_filter($roles, function ($role) { + return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); }); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); } @@ -495,9 +493,7 @@ App::post('/v1/teams/:teamId/memberships') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->inject('proofForPassword') - ->inject('proofForToken') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) { + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { $isAppUser = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -570,7 +566,6 @@ App::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); - $hash = $proofForPassword->hash($proofForPassword->generate()); $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ @@ -584,9 +579,9 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => true, // TODO: Set password empty? - 'password' => $hash, - 'hash' => $proofForPassword->getHash()->getName(), - 'hashOptions' => $proofForPassword->getHash()->getOptions(), + 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, /** * Set the password update time to 0 for users created using * team invite and OAuth to allow password updates without an @@ -618,7 +613,7 @@ App::post('/v1/teams/:teamId/memberships') Query::equal('teamInternalId', [$team->getSequence()]), ]); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(); if ($membership->isEmpty()) { $membershipId = ID::unique(); $membership = new Document([ @@ -638,7 +633,7 @@ App::post('/v1/teams/:teamId/memberships') 'invited' => DateTime::now(), 'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null, 'confirm' => ($isPrivilegedUser || $isAppUser), - 'secret' => $proofForToken->hash($secret), + 'secret' => Auth::hash($secret), 'search' => implode(' ', [$membershipId, $invitee->getId()]) ]); @@ -651,7 +646,7 @@ App::post('/v1/teams/:teamId/memberships') } } elseif ($membership->getAttribute('confirm') === false) { - $membership->setAttribute('secret', $proofForToken->hash($secret)); + $membership->setAttribute('secret', Auth::hash($secret)); $membership->setAttribute('invited', DateTime::now()); if ($isPrivilegedUser || $isAppUser) { @@ -1075,7 +1070,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') if ($project->getId() === 'console') { $roles = array_keys(Config::getParam('roles', [])); array_filter($roles, function ($role) { - return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]); + return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); }); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); } @@ -1190,9 +1185,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('project') ->inject('geodb') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForToken') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) { + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1211,7 +1204,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH); } - if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) { + if (Auth::hash($secret) !== $membership->getAttribute('secret')) { throw new Exception(Exception::TEAM_INVALID_SECRET); } @@ -1245,9 +1238,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::addSeconds(new \DateTime(), $authDuration); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(); $session = new Document(array_merge([ '$id' => ID::unique(), '$permissions' => [ @@ -1257,9 +1250,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ], 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $user->getAttribute('email'), - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), 'factors' => ['email'], @@ -1271,19 +1264,14 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Authorization::setRole(Role::user($userId)->toString()); - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $response ->addCookie( - name: $store->getKey() . '_legacy', - value: $encoded, + name: Auth::$cookieName . '_legacy', + value: Auth::encodeSession($user->getId(), $secret), expire: (new \DateTime($expire))->getTimestamp(), path: '/', domain: Config::getParam('cookieDomain'), @@ -1291,8 +1279,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') httponly: true ) ->addCookie( - name: $store->getKey(), - value: $encoded, + name: Auth::$cookieName, + value: Auth::encodeSession($user->getId(), $secret), expire: (new \DateTime($expire))->getTimestamp(), path: '/', domain: Config::getParam('cookieDomain'), diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 1dfa5c2603..5498a33bf5 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1,6 +1,7 @@ getAttribute('auths', [])['passwordHistory'] ?? 0; if (!empty($email)) { @@ -107,18 +97,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor } } - $hashedPassword = null; - - if (!empty($password)) { - if ($hash instanceof Plaintext) { // Password was never hashed, hash it with the default hash - $defaultHash = new ProofsPassword(); - $hashedPassword = $defaultHash->hash($password); - $hash = $defaultHash->getHash(); - } else { - $hashedPassword = $password; - } - } - + $password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null; $user = new Document([ '$id' => $userId, '$permissions' => [ @@ -132,11 +111,11 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor 'phoneVerification' => false, 'status' => true, 'labels' => [], - 'password' => $hashedPassword, - 'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword], - 'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null, - 'hash' => $hash->getName(), - 'hashOptions' => $hash->getOptions(), + 'password' => $password, + 'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password], + 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, + 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, + 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash], 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -147,7 +126,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor 'search' => implode(' ', [$userId, $email, $phone, $name]), ]); - if ($hash instanceof Plaintext) { + if ($hash === 'plaintext') { $hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]); } @@ -238,9 +217,7 @@ App::post('/v1/users') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $plaintext = new Plaintext(); - - $user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -274,10 +251,7 @@ App::post('/v1/users/bcrypt') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $bcrypt = new Bcrypt(); - $bcrypt->setCost(8); // Default cost - - $user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -312,9 +286,7 @@ App::post('/v1/users/md5') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $md5 = new MD5(); - - $user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -349,13 +321,7 @@ App::post('/v1/users/argon2') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $argon2 = new Argon2(); - $argon2 - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); - - $user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -391,12 +357,13 @@ App::post('/v1/users/sha') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $sha = new Sha(); + $options = '{}'; + if (!empty($passwordVersion)) { - $sha->setVersion($passwordVersion); + $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -431,9 +398,7 @@ App::post('/v1/users/phpass') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $phpass = new PHPass(); - - $user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -473,15 +438,15 @@ App::post('/v1/users/scrypt') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $scrypt = new Scrypt(); - $scrypt - ->setSalt($passwordSalt) - ->setCpuCost($passwordCpu) - ->setMemoryCost($passwordMemory) - ->setParallelCost($passwordParallel) - ->setLength($passwordLength); + $options = [ + 'salt' => $passwordSalt, + 'costCpu' => $passwordCpu, + 'costMemory' => $passwordMemory, + 'costParallel' => $passwordParallel, + 'length' => $passwordLength + ]; - $user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -519,13 +484,7 @@ App::post('/v1/users/scrypt-modified') ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $scryptModified = new ScryptModified(); - $scryptModified - ->setSalt($passwordSalt) - ->setSaltSeparator($passwordSaltSeparator) - ->setSignerKey($passwordSignerKey); - - $user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1110,6 +1069,7 @@ App::get('/v1/users/identities') } catch (QueryException $e) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } + if (!empty($search)) { $queries[] = Query::search('search', $search); } @@ -1376,21 +1336,12 @@ App::patch('/v1/users/:userId/password') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); - // Create Argon2 hasher with default settings - $hasher = new Argon2(); - $hasher - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $newPassword = $hasher->hash($password); - - $hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = $user->getAttribute('passwordHistory', []); - if ($historyLimit > 0) { - $validator = new PasswordHistory($history, $hash); + $validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions')); if (!$validator->isValid($password)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED); } @@ -1403,8 +1354,8 @@ App::patch('/v1/users/:userId/password') ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) ->setAttribute('passwordUpdate', DateTime::now()) - ->setAttribute('hash', $hasher->getName()) - ->setAttribute('hashOptions', $hasher->getOptions()); + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -2217,19 +2168,17 @@ App::post('/v1/users/:userId/sessions') ->inject('locale') ->inject('geodb') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForToken') - ->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) { + ->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION); $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $session = new Document(array_merge( @@ -2237,8 +2186,8 @@ App::post('/v1/users/:userId/sessions') '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'provider' => SESSION_PROVIDER_SERVER, - 'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak + 'provider' => Auth::SESSION_PROVIDER_SERVER, + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'factors' => ['server'], 'ip' => $request->getIP(), @@ -2262,13 +2211,8 @@ App::post('/v1/users/:userId/sessions') $dbForProject->purgeCachedDocument('users', $user->getId()); - $encoded = $store - ->setProperty('id', $user->getId()) - ->setProperty('secret', $secret) - ->encode(); - $session - ->setAttribute('secret', $encoded) + ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ->setAttribute('countryName', $countryName); $queueForEvents @@ -2303,7 +2247,7 @@ App::post('/v1/users/:userId/tokens') )) ->param('userId', '', new UID(), 'User ID.') ->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true) - ->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true) + ->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true) ->inject('request') ->inject('response') ->inject('dbForProject') @@ -2315,17 +2259,15 @@ App::post('/v1/users/:userId/tokens') throw new Exception(Exception::USER_NOT_FOUND); } - $proofForToken = new Token($length); - $proofForToken->setHash(new Sha()); - $secret = $proofForToken->generate(); + $secret = Auth::tokenGenerator($length); $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire)); $token = new Document([ '$id' => ID::unique(), 'userId' => $user->getId(), 'userInternalId' => $user->getSequence(), - 'type' => TOKEN_TYPE_GENERIC, - 'secret' => $proofForToken->hash($secret), + 'type' => Auth::TOKEN_TYPE_GENERIC, + 'secret' => Auth::hash($secret), 'expire' => $expire, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP() diff --git a/app/controllers/general.php b/app/controllers/general.php index 5ab30ee885..07de95a38f 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1185,29 +1185,17 @@ App::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { - $logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning'; - $logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]'; - - Console::{$logLevel}($logPrefix . ' Timestamp: ' . date('c', time())); + Console::error('[Error] Timestamp: ' . date('c', time())); if ($route) { - Console::{$logLevel}($logPrefix . ' Status Code: ' . $code); - Console::{$logLevel}($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath()); + Console::error('[Error] Method: ' . $route->getMethod()); + Console::error('[Error] URL: ' . $route->getPath()); } - Console::{$logLevel}($logPrefix . ' Type: ' . get_class($error)); - Console::{$logLevel}($logPrefix . ' Message: ' . $message); - Console::{$logLevel}($logPrefix . ' File: ' . $file); - Console::{$logLevel}($logPrefix . ' Line: ' . $line); - Console::{$logLevel}($logPrefix . ' Trace:'); - foreach ($trace as $index => $entry) { - $traceFile = $entry['file'] ?? 'unknown'; - $traceLine = $entry['line'] ?? 0; - $traceFunction = $entry['function'] ?? 'unknown'; - $traceClass = $entry['class'] ?? ''; - $traceType = $entry['type'] ?? ''; - Console::{$logLevel}(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()"); - } - Console::{$logLevel}(''); + + Console::error('[Error] Type: ' . get_class($error)); + Console::error('[Error] Message: ' . $message); + Console::error('[Error] File: ' . $file); + Console::error('[Error] Line: ' . $line); } switch ($class) { diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 203433ead3..959ee77b7d 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -222,94 +222,39 @@ App::init() ->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) { $route = $utopia->getRoute(); - /** - * Handle user authentication and session validation. - * - * This function follows a series of steps to determine the appropriate user session - * based on cookies, headers, and JWT tokens. - * - * Process: - * - * Project & Role Validation: - * 1. Check if the project is empty. If so, throw an exception. - * 2. Get the roles configuration. - * 3. Determine the role for the user based on the user document. - * 4. Get the scopes for the role. - * - * API Key Authentication: - * 5. If there is an API key: - * - Verify no user session exists simultaneously - * - Check if key is expired - * - Set role and scopes from API key - * - Handle special app role case - * - For standard keys, update last accessed time - * - * User Activity: - * 6. If the project is not the console and user is not admin: - * - Update user's last activity timestamp - * - * Access Control: - * 7. Get the method from the route - * 8. Validate namespace permissions - * 9. Validate scope permissions - * 10. Check if user is blocked - * - * Security Checks: - * 11. Verify password status (check if reset required) - * 12. Validate MFA requirements: - * - Check if MFA is enabled - * - Verify email status - * - Verify phone status - * - Verify authenticator status - * 13. Handle Multi-Factor Authentication: - * - Check remaining required factors - * - Validate factor completion - * - Throw exception if factors incomplete - */ - - // Step 1: Check if project is empty if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - // Step 2: Get roles configuration $roles = Config::getParam('roles', []); - // Step 3: Determine role for user - // TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token. - $role = $user->isEmpty() ? Role::guests()->toString() : Role::users()->toString(); - // Step 4: Get scopes for the role $scopes = $roles[$role]['scopes']; - // Step 5: API Key Authentication + // API Key authentication if (!empty($apiKey)) { - // Verify no user session exists simultaneously if (!$user->isEmpty()) { throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET); } - // Check if key is expired if ($apiKey->isExpired()) { throw new Exception(Exception::PROJECT_KEY_EXPIRED); } - // Set role and scopes from API key $role = $apiKey->getRole(); $scopes = $apiKey->getScopes(); - // Handle special app role case - if ($apiKey->getRole() === USER_ROLE_APPS) { + if ($apiKey->getRole() === Auth::USER_ROLE_APPS) { // Disable authorization checks for API keys Authorization::setDefaultStatus(false); $user = new Document([ '$id' => '', 'status' => true, - 'type' => ACTIVITY_TYPE_APP, + 'type' => Auth::ACTIVITY_TYPE_APP, 'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(), 'password' => '', 'name' => $apiKey->getName(), @@ -318,7 +263,6 @@ App::init() $queueForAudits->setUser($user); } - // For standard keys, update last accessed time if ($apiKey->getType() === API_KEY_STANDARD) { $dbKey = $project->find( key: 'secret', @@ -388,7 +332,7 @@ App::init() Authorization::setRole($authRole); } - // Step 6: Update project and user last activity + // Update project last activity if (!$project->isEmpty() && $project->getId() !== 'console') { $accessedAt = $project->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { @@ -397,6 +341,7 @@ App::init() } } + // Update user last activity if (!empty($user->getId())) { $accessedAt = $user->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) { @@ -410,7 +355,6 @@ App::init() } } - // Steps 7-9: Access Control - Method, Namespace and Scope Validation /** * @var ?Method $method */ @@ -434,23 +378,21 @@ App::init() } } - // Step 9: Validate scope permissions + // Do now allow access if scope is not allowed $allowed = (array)$route->getLabel('scope', 'none'); if (empty(\array_intersect($allowed, $scopes))) { throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')'); } - // Step 10: Check if user is blocked + // Do not allow access to blocked accounts if (false === $user->getAttribute('status')) { // Account is blocked throw new Exception(Exception::USER_BLOCKED); } - // Step 11: Verify password status if ($user->getAttribute('reset')) { throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED); } - // Step 12: Validate MFA requirements $mfaEnabled = $user->getAttribute('mfa', false); $hasVerifiedEmail = $user->getAttribute('emailVerification', false); $hasVerifiedPhone = $user->getAttribute('phoneVerification', false); @@ -458,7 +400,6 @@ App::init() $hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator; $minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1; - // Step 13: Handle Multi-Factor Authentication if (!in_array('mfa', $route->getGroups())) { if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) { throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED); @@ -585,7 +526,7 @@ App::init() if (!$user->isEmpty()) { $userClone = clone $user; // $user doesn't support `type` and can cause unintended effects. - $userClone->setAttribute('type', ACTIVITY_TYPE_USER); + $userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER); $queueForAudits->setUser($userClone); } @@ -825,7 +766,7 @@ App::shutdown() if (!$user->isEmpty()) { $userClone = clone $user; // $user doesn't support `type` and can cause unintended effects. - $userClone->setAttribute('type', ACTIVITY_TYPE_USER); + $userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER); $queueForAudits->setUser($userClone); } elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) { /** @@ -839,7 +780,7 @@ App::shutdown() $user = new Document([ '$id' => '', 'status' => true, - 'type' => ACTIVITY_TYPE_GUEST, + 'type' => Auth::ACTIVITY_TYPE_GUEST, 'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(), 'password' => '', 'name' => 'Guest', diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 8f5e981362..ecabc641ec 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -20,7 +20,7 @@ App::init() $lastUpdate = $session->getAttribute('mfaUpdatedAt'); if (!empty($lastUpdate)) { $now = DateTime::now(); - $maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge + $maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge $isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now); } diff --git a/app/init/constants.php b/app/init/constants.php index aaa3e1e206..3c8485aa4f 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -92,72 +92,6 @@ const APP_VCS_GITHUB_USERNAME = 'Appwrite'; const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled'; -// User Roles -const USER_ROLE_ANY = 'any'; -const USER_ROLE_GUESTS = 'guests'; -const USER_ROLE_USERS = 'users'; -const USER_ROLE_ADMIN = 'admin'; -const USER_ROLE_DEVELOPER = 'developer'; -const USER_ROLE_OWNER = 'owner'; -const USER_ROLE_APPS = 'apps'; -const USER_ROLE_SYSTEM = 'system'; - -/** - * Token Expiration times. - */ -const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */ -const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */ -const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */ -const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */ -const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */ -const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */ - -/** - * Token Lengths. - */ -const TOKEN_LENGTH_MAGIC_URL = 64; -const TOKEN_LENGTH_VERIFICATION = 256; -const TOKEN_LENGTH_RECOVERY = 256; -const TOKEN_LENGTH_OAUTH2 = 64; -const TOKEN_LENGTH_SESSION = 256; - -/** - * Token Types. - */ -const TOKEN_TYPE_LOGIN = 1; // Deprecated -const TOKEN_TYPE_VERIFICATION = 2; -const TOKEN_TYPE_RECOVERY = 3; -const TOKEN_TYPE_INVITE = 4; -const TOKEN_TYPE_MAGIC_URL = 5; -const TOKEN_TYPE_PHONE = 6; -const TOKEN_TYPE_OAUTH2 = 7; -const TOKEN_TYPE_GENERIC = 8; -const TOKEN_TYPE_EMAIL = 9; // OTP - -/** - * Session Providers. - */ -const SESSION_PROVIDER_EMAIL = 'email'; -const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; -const SESSION_PROVIDER_MAGIC_URL = 'magic-url'; -const SESSION_PROVIDER_PHONE = 'phone'; -const SESSION_PROVIDER_OAUTH2 = 'oauth2'; -const SESSION_PROVIDER_TOKEN = 'token'; -const SESSION_PROVIDER_SERVER = 'server'; - -/** - * Activity associated with user or the app. - */ -const ACTIVITY_TYPE_APP = 'app'; -const ACTIVITY_TYPE_USER = 'user'; -const ACTIVITY_TYPE_GUEST = 'guest'; - -/** - * MFA - */ -const MFA_RECENT_DURATION = 1800; // 30 mins - - // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; diff --git a/app/init/resources.php b/app/init/resources.php index 48a6a102e3..f91d18f698 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -24,16 +24,9 @@ use Appwrite\GraphQL\Schema; use Appwrite\Network\Platform; use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; -use Appwrite\Utopia\Response; use Executor\Executor; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; -use Utopia\Auth\Hashes\Argon2; -use Utopia\Auth\Hashes\Sha; -use Utopia\Auth\Proofs\Code; -use Utopia\Auth\Proofs\Password; -use Utopia\Auth\Proofs\Token; -use Utopia\Auth\Store; use Utopia\Cache\Adapter\Pool as CachePool; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; @@ -233,93 +226,72 @@ App::setResource('platforms', function (Request $request, Document $console, Doc ]; }, ['request', 'console', 'project', 'dbForPlatform']); -App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) { +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Database $dbForPlatform */ /** @var string $mode */ - /** @var Utopia\Auth\Store $store */ - - /** - * Handles user authentication and session validation. - * - * This function follows a series of steps to determine the appropriate user session - * based on cookies, headers, and JWT tokens. - * - * Process: - * 1. Checks the cookie based on mode: - * - If in admin mode, uses console project id for key. - * - Otherwise, sets the key using the project ID - * 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`. - * - If this method is used, returns the header: `X-Debug-Fallback: true`. - * 3. Fetches the user document from the appropriate database based on the mode. - * 4. If the user document is empty or the session key cannot be verified, sets an empty user document. - * 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token. - * 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`, - * overwriting the previous value. - */ Authorization::setDefaultStatus(true); - $store->setKey('a_session_' . $project->getId()); + Auth::setCookieName('a_session_' . $project->getId()); if (APP_MODE_ADMIN === $mode) { - $store->setKey('a_session_' . $console->getId()); + Auth::setCookieName('a_session_' . $console->getId()); } - $store->decode( + $session = Auth::decodeSession( $request->getCookie( - $store->getKey(), // Get sessions - $request->getCookie($store->getKey() . '_legacy', '') + Auth::$cookieName, // Get sessions + $request->getCookie(Auth::$cookieName . '_legacy', '') ) ); // Get session from header for SSR clients - if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { + if (empty($session['id']) && empty($session['secret'])) { $sessionHeader = $request->getHeader('x-appwrite-session', ''); if (!empty($sessionHeader)) { - $store->decode($sessionHeader); + $session = Auth::decodeSession($sessionHeader); } } // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies - if ($response) { // if in http context - add debug header + if ($response) { $response->addHeader('X-Debug-Fallback', 'false'); } - if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { + if (empty($session['id']) && empty($session['secret'])) { if ($response) { $response->addHeader('X-Debug-Fallback', 'true'); } $fallback = $request->getHeader('x-fallback-cookies', ''); $fallback = \json_decode($fallback, true); - $store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '')); + $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); } + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; + $user = new Document([]); - if (APP_MODE_ADMIN === $mode) { - $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); - } else { - if ($project->isEmpty()) { - $user = new Document([]); - } else { - if (!empty($store->getProperty('id', ''))) { - if ($project->getId() === 'console') { - $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); - } else { - $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); - } + if (!empty(Auth::$unique)) { + if ($mode === APP_MODE_ADMIN) { + $user = $dbForPlatform->getDocument('users', Auth::$unique); + } elseif (!$project->isEmpty()) { + if ($project->getId() === 'console') { + $user = $dbForPlatform->getDocument('users', Auth::$unique); + } else { + $user = $dbForProject->getDocument('users', Auth::$unique); } } } if ( $user->isEmpty() // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) ) { // Validate user has valid login token $user = new Document([]); } @@ -364,7 +336,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co $dbForPlatform->setMetadata('user', $user->getId()); return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']); +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']); App::setResource('project', function ($dbForPlatform, $request, $console) { /** @var Appwrite\Utopia\Request $request */ @@ -382,13 +354,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) { return $project; }, ['dbForPlatform', 'request', 'console']); -App::setResource('session', function (Document $user, Store $store, Token $proofForToken) { +App::setResource('session', function (Document $user) { if ($user->isEmpty()) { return; } $sessions = $user->getAttribute('sessions', []); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); if (!$sessionId) { return; @@ -401,7 +373,7 @@ App::setResource('session', function (Document $user, Store $store, Token $proof } return; -}, ['user', 'store', 'proofForToken']); +}, ['user']); App::setResource('console', function () { return new Document(Config::getParam('console')); @@ -975,37 +947,6 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); - -App::setResource('store', function (): Store { - return new Store(); -}); - -App::setResource('proofForPassword', function (): Password { - $hash = new Argon2(); - $hash - ->setMemoryCost(2048) - ->setTimeCost(4) - ->setThreads(3); - - $password = new Password(); - $password - ->setHash($hash); - - return $password; -}); - -App::setResource('proofForToken', function (): Token { - $token = new Token(); - $token->setHash(new Sha()); - return $token; -}); - -App::setResource('proofForCode', function (): Code { - $code = new Code(); - $code->setHash(new Sha()); - return $code; -}); - App::setResource('executor', fn () => new Executor()); App::setResource('resourceToken', function ($project, $dbForProject, $request) { diff --git a/app/realtime.php b/app/realtime.php index 6084d32df1..e18ab8e10d 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -16,9 +16,6 @@ use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; -use Utopia\Auth\Hashes\Sha; -use Utopia\Auth\Proofs\Token; -use Utopia\Auth\Store; use Utopia\Cache\Adapter\Pool as CachePool; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; @@ -681,24 +678,15 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.'); } - $store = new Store(); + $session = Auth::decodeSession($message['data']['session']); + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; - $store->decode($message['data']['session']); - - $user = $database->getDocument('users', $store->getProperty('id', '')); - - /** - * TODO: - * Moving forward, we should try to use our dependency injection container - * to inject the proof for token. - * This way we will have one source of truth for the proof for token. - */ - $proofForToken = new Token(); - $proofForToken->setHash(new Sha()); + $user = $database->getDocument('users', Auth::$unique); if ( empty($user->getId()) // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token ) { // cookie not valid throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); diff --git a/composer.json b/composer.json index df6fe95d3a..bb843fd771 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,6 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.19.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/auth": "0.4.*", "utopia-php/abuse": "1.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "1.*", diff --git a/composer.lock b/composer.lock index f65e715647..f2efa0d785 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": "3e8df036b4cb47d2eae34be382e04800", + "content-hash": "568800edca746c4e8d0d50648b25f589", "packages": [ { "name": "adhocore/jwt", @@ -3592,61 +3592,6 @@ }, "time": "2025-10-20T07:14:26+00:00" }, - { - "name": "utopia-php/auth", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/auth.git", - "reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/auth/zipball/02415e1a89cdbc14e3e16a7856ecf7f868869449", - "reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449", - "shasum": "" - }, - "require": { - "ext-hash": "*", - "ext-scrypt": "*", - "ext-sodium": "*", - "php": ">=8.0" - }, - "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", - "phpunit/phpunit": "^9.3", - "vimeo/psalm": "4.0.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Auth\\": "src/Auth" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Utopia PHP", - "email": "team@appwrite.io" - } - ], - "description": "A simple PHP authentication library", - "keywords": [ - "Authentication", - "auth", - "php", - "security" - ], - "support": { - "issues": "https://github.com/utopia-php/auth/issues", - "source": "https://github.com/utopia-php/auth/tree/0.4.0" - }, - "time": "2025-04-29T19:29:28+00:00" - }, { "name": "utopia-php/cache", "version": "0.13.1", diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 86d1e197bf..9af5045fa4 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -2,8 +2,13 @@ namespace Appwrite\Auth; -use Utopia\Auth\Proof; -use Utopia\Auth\Proofs\Token; +use Appwrite\Auth\Hash\Argon2; +use Appwrite\Auth\Hash\Bcrypt; +use Appwrite\Auth\Hash\Md5; +use Appwrite\Auth\Hash\Phpass; +use Appwrite\Auth\Hash\Scrypt; +use Appwrite\Auth\Hash\Scryptmodified; +use Appwrite\Auth\Hash\Sha; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; @@ -12,45 +17,186 @@ use Utopia\Database\Validator\Roles; class Auth { + public const SUPPORTED_ALGOS = [ + 'argon2', + 'bcrypt', + 'md5', + 'sha', + 'phpass', + 'scrypt', + 'scryptMod', + 'plaintext' + ]; + + public const DEFAULT_ALGO = 'argon2'; + public const DEFAULT_ALGO_OPTIONS = ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3]; + + /** + * User Roles. + */ + public const USER_ROLE_ANY = 'any'; + public const USER_ROLE_GUESTS = 'guests'; + public const USER_ROLE_USERS = 'users'; + public const USER_ROLE_ADMIN = 'admin'; + public const USER_ROLE_DEVELOPER = 'developer'; + public const USER_ROLE_OWNER = 'owner'; + public const USER_ROLE_APPS = 'apps'; + public const USER_ROLE_SYSTEM = 'system'; + + /** + * Activity associated with user or the app. + */ + public const ACTIVITY_TYPE_APP = 'app'; + public const ACTIVITY_TYPE_USER = 'user'; + public const ACTIVITY_TYPE_GUEST = 'guest'; + + /** + * Token Types. + */ + public const TOKEN_TYPE_LOGIN = 1; // Deprecated + public const TOKEN_TYPE_VERIFICATION = 2; + public const TOKEN_TYPE_RECOVERY = 3; + public const TOKEN_TYPE_INVITE = 4; + public const TOKEN_TYPE_MAGIC_URL = 5; + public const TOKEN_TYPE_PHONE = 6; + public const TOKEN_TYPE_OAUTH2 = 7; + public const TOKEN_TYPE_GENERIC = 8; + public const TOKEN_TYPE_EMAIL = 9; // OTP + + /** + * Session Providers. + */ + public const SESSION_PROVIDER_EMAIL = 'email'; + public const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; + public const SESSION_PROVIDER_MAGIC_URL = 'magic-url'; + public const SESSION_PROVIDER_PHONE = 'phone'; + public const SESSION_PROVIDER_OAUTH2 = 'oauth2'; + public const SESSION_PROVIDER_TOKEN = 'token'; + public const SESSION_PROVIDER_SERVER = 'server'; + + /** + * Token Expiration times. + */ + public const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */ + public const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */ + public const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */ + public const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */ + public const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */ + public const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */ + + /** + * Token Lengths. + */ + public const TOKEN_LENGTH_MAGIC_URL = 64; + public const TOKEN_LENGTH_VERIFICATION = 256; + public const TOKEN_LENGTH_RECOVERY = 256; + public const TOKEN_LENGTH_OAUTH2 = 64; + public const TOKEN_LENGTH_SESSION = 256; + + /** + * MFA + */ + public const MFA_RECENT_DURATION = 1800; // 30 mins + + /** + * @var string + */ + public static $cookieName = 'a_session'; + /** * @var string - * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. */ public static $cookieNamePreview = 'a_jwt_console'; /** - * Token type to session provider mapping. + * User Unique ID. * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. - * @param int $type + * @var string + */ + public static $unique = ''; + + /** + * User Secret Key. + * + * @var string + */ + public static $secret = ''; + + /** + * Set Cookie Name. + * + * @param $string * * @return string */ + public static function setCookieName($string) + { + return self::$cookieName = $string; + } + + /** + * Encode Session. + * + * @param string $id + * @param string $secret + * + * @return string + */ + public static function encodeSession($id, $secret) + { + return \base64_encode(\json_encode([ + 'id' => $id, + 'secret' => $secret, + ])); + } + + /** + * Token type to session provider mapping. + */ public static function getSessionProviderByTokenType(int $type): string { switch ($type) { - case TOKEN_TYPE_VERIFICATION: - case TOKEN_TYPE_RECOVERY: - case TOKEN_TYPE_INVITE: - return SESSION_PROVIDER_EMAIL; - case TOKEN_TYPE_MAGIC_URL: - return SESSION_PROVIDER_MAGIC_URL; - case TOKEN_TYPE_PHONE: - return SESSION_PROVIDER_PHONE; - case TOKEN_TYPE_OAUTH2: - return SESSION_PROVIDER_OAUTH2; + case Auth::TOKEN_TYPE_VERIFICATION: + case Auth::TOKEN_TYPE_RECOVERY: + case Auth::TOKEN_TYPE_INVITE: + return Auth::SESSION_PROVIDER_EMAIL; + case Auth::TOKEN_TYPE_MAGIC_URL: + return Auth::SESSION_PROVIDER_MAGIC_URL; + case Auth::TOKEN_TYPE_PHONE: + return Auth::SESSION_PROVIDER_PHONE; + case Auth::TOKEN_TYPE_OAUTH2: + return Auth::SESSION_PROVIDER_OAUTH2; default: - return SESSION_PROVIDER_TOKEN; + return Auth::SESSION_PROVIDER_TOKEN; } } + /** + * Decode Session. + * + * @param string $session + * + * @return array + * + * @throws \Exception + */ + public static function decodeSession($session) + { + $session = \json_decode(\base64_decode($session), true); + $default = ['id' => null, 'secret' => '']; + + if (!\is_array($session)) { + return $default; + } + + return \array_merge($default, $session); + } + /** * Encode. * * One-way encryption * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param $string * * @return string @@ -60,12 +206,124 @@ class Auth return \hash('sha256', $string); } + /** + * Password Hash. + * + * One way string hashing for user passwords + * + * @param string $string + * @param string $algo hashing algorithm to use + * @param array $options algo-specific options + * + * @return bool|string|null + */ + public static function passwordHash(string $string, string $algo, array $options = []) + { + // Plain text not supported, just an alias. Switch to recommended algo + if ($algo === 'plaintext') { + $algo = Auth::DEFAULT_ALGO; + $options = Auth::DEFAULT_ALGO_OPTIONS; + } + + if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) { + throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); + } + + switch ($algo) { + case 'argon2': + $hasher = new Argon2($options); + return $hasher->hash($string); + case 'bcrypt': + $hasher = new Bcrypt($options); + return $hasher->hash($string); + case 'md5': + $hasher = new Md5($options); + return $hasher->hash($string); + case 'sha': + $hasher = new Sha($options); + return $hasher->hash($string); + case 'phpass': + $hasher = new Phpass($options); + return $hasher->hash($string); + case 'scrypt': + $hasher = new Scrypt($options); + return $hasher->hash($string); + case 'scryptMod': + $hasher = new Scryptmodified($options); + return $hasher->hash($string); + default: + throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); + } + } + + /** + * Password verify. + * + * @param string $plain + * @param string $hash + * @param string $algo hashing algorithm used to hash + * @param array $options algo-specific options + * + * @return bool + */ + public static function passwordVerify(string $plain, string $hash, string $algo, array $options = []) + { + // Plain text not supported, just an alias. Switch to recommended algo + if ($algo === 'plaintext') { + $algo = Auth::DEFAULT_ALGO; + $options = Auth::DEFAULT_ALGO_OPTIONS; + } + + if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) { + throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); + } + + switch ($algo) { + case 'argon2': + $hasher = new Argon2($options); + return $hasher->verify($plain, $hash); + case 'bcrypt': + $hasher = new Bcrypt($options); + return $hasher->verify($plain, $hash); + case 'md5': + $hasher = new Md5($options); + return $hasher->verify($plain, $hash); + case 'sha': + $hasher = new Sha($options); + return $hasher->verify($plain, $hash); + case 'phpass': + $hasher = new Phpass($options); + return $hasher->verify($plain, $hash); + case 'scrypt': + $hasher = new Scrypt($options); + return $hasher->verify($plain, $hash); + case 'scryptMod': + $hasher = new Scryptmodified($options); + return $hasher->verify($plain, $hash); + default: + throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); + } + } + + /** + * Password Generator. + * + * Generate random password string + * + * @param int $length + * + * @return string + */ + public static function passwordGenerator(int $length = 20): string + { + return \bin2hex(\random_bytes($length)); + } + /** * Token Generator. * * Generate random password string * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param int $length Length of returned token * * @return string @@ -82,17 +340,36 @@ class Auth return substr($token, 0, $length); } + /** + * Code Generator. + * + * Generate random code string + * + * @param int $length + * + * @return string + */ + public static function codeGenerator(int $length = 6): string + { + $value = ''; + + for ($i = 0; $i < $length; $i++) { + $value .= random_int(0, 9); + } + + return $value; + } + /** * Verify token and check that its not expired. * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $tokens * @param int $type Type of token to verify, if null will verify any type * @param string $secret * * @return false|Document */ - public static function tokenVerify(array $tokens, int $type = null, string $secret, Proof $proofForToken): false|Document + public static function tokenVerify(array $tokens, int $type = null, string $secret): false|Document { foreach ($tokens as $token) { if ( @@ -100,7 +377,7 @@ class Auth $token->isSet('expire') && $token->isSet('type') && ($type === null || $token->getAttribute('type') === $type) && - $proofForToken->verify($secret, $token->getAttribute('secret')) && + $token->getAttribute('secret') === self::hash($secret) && DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now()) ) { return $token; @@ -113,19 +390,18 @@ class Auth /** * Verify session and check that its not expired. * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $sessions * @param string $secret * * @return bool|string */ - public static function sessionVerify(array $sessions, string $secret, Token $proofForToken) + public static function sessionVerify(array $sessions, string $secret) { foreach ($sessions as $session) { if ( $session->isSet('secret') && $session->isSet('provider') && - $proofForToken->verify($secret, $session->getAttribute('secret')) && + $session->getAttribute('secret') === self::hash($secret) && DateTime::formatTz(DateTime::format(new \DateTime($session->getAttribute('expire')))) >= DateTime::formatTz(DateTime::now()) ) { return $session->getId(); @@ -138,7 +414,6 @@ class Auth /** * Is Privileged User? * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $roles * * @return bool @@ -146,9 +421,9 @@ class Auth public static function isPrivilegedUser(array $roles): bool { if ( - in_array(USER_ROLE_OWNER, $roles) || - in_array(USER_ROLE_DEVELOPER, $roles) || - in_array(USER_ROLE_ADMIN, $roles) + in_array(self::USER_ROLE_OWNER, $roles) || + in_array(self::USER_ROLE_DEVELOPER, $roles) || + in_array(self::USER_ROLE_ADMIN, $roles) ) { return true; } @@ -159,14 +434,13 @@ class Auth /** * Is App User? * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param array $roles * * @return bool */ public static function isAppUser(array $roles): bool { - if (in_array(USER_ROLE_APPS, $roles)) { + if (in_array(self::USER_ROLE_APPS, $roles)) { return true; } @@ -176,7 +450,6 @@ class Auth /** * Returns all roles for a user. * - * @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible. * @param Document $user * @return array */ @@ -227,4 +500,16 @@ class Auth return $roles; } + + /** + * Check if user is anonymous. + * + * @param Document $user + * @return bool + */ + public static function isAnonymousUser(Document $user): bool + { + return is_null($user->getAttribute('email')) + && is_null($user->getAttribute('phone')); + } } diff --git a/src/Appwrite/Auth/Hash.php b/src/Appwrite/Auth/Hash.php new file mode 100644 index 0000000000..7134057581 --- /dev/null +++ b/src/Appwrite/Auth/Hash.php @@ -0,0 +1,62 @@ +setOptions($options); + } + + /** + * Set hashing algo options + * + * @param array $options Hashing-algo specific options + */ + public function setOptions(array $options): self + { + $this->options = \array_merge([], $this->getDefaultOptions(), $options); + return $this; + } + + /** + * Get hashing algo options + * + * @return array $options Hashing-algo specific options + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @param string $password Input password to hash + * + * @return string hash + */ + abstract public function hash(string $password): string; + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + abstract public function verify(string $password, string $hash): bool; + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + abstract public function getDefaultOptions(): array; +} diff --git a/src/Appwrite/Auth/Hash/Argon2.php b/src/Appwrite/Auth/Hash/Argon2.php new file mode 100644 index 0000000000..c723b077b1 --- /dev/null +++ b/src/Appwrite/Auth/Hash/Argon2.php @@ -0,0 +1,47 @@ +getOptions()); + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + return \password_verify($password, $hash); + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3]; + } +} diff --git a/src/Appwrite/Auth/Hash/Bcrypt.php b/src/Appwrite/Auth/Hash/Bcrypt.php new file mode 100644 index 0000000000..8b6177f33a --- /dev/null +++ b/src/Appwrite/Auth/Hash/Bcrypt.php @@ -0,0 +1,46 @@ +getOptions()); + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + return \password_verify($password, $hash); + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return [ 'cost' => 8 ]; + } +} diff --git a/src/Appwrite/Auth/Hash/Md5.php b/src/Appwrite/Auth/Hash/Md5.php new file mode 100644 index 0000000000..8ade3dd5e2 --- /dev/null +++ b/src/Appwrite/Auth/Hash/Md5.php @@ -0,0 +1,44 @@ +hash($password) === $hash; + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return []; + } +} diff --git a/src/Appwrite/Auth/Hash/Phpass.php b/src/Appwrite/Auth/Hash/Phpass.php new file mode 100644 index 0000000000..988c38cc8d --- /dev/null +++ b/src/Appwrite/Auth/Hash/Phpass.php @@ -0,0 +1,290 @@ + in 2004-2017 and placed in + * the public domain. Revised in subsequent years, still public domain. + * There's absolutely no warranty. + * The homepage URL for the source framework is: http://www.openwall.com/phpass/ + * Please be sure to update the Version line if you edit this file in any way. + * It is suggested that you leave the main version number intact, but indicate + * your project name (after the slash) and add your own revision information. + * Please do not change the "private" password hashing method implemented in + * here, thereby making your hashes incompatible. However, if you must, please + * change the hash type identifier (the "$P$") to something different. + * Obviously, since this code is in the public domain, the above are not + * requirements (there can be none), but merely suggestions. + * + * @author Solar Designer + * @copyright Copyright (C) 2017 All rights reserved. + * @license http://www.opensource.org/licenses/mit-license.html MIT License; see LICENSE.txt + */ + +namespace Appwrite\Auth\Hash; + +use Appwrite\Auth\Hash; + +/* + * PHPass accepted options: + * int iteration_count_log2; The Logarithmic cost value used when generating hash values indicating the number of rounds used to generate hashes + * string portable_hashes + * string random_state; The cached random state + * + * Reference: https://github.com/photodude/phpass +*/ +class Phpass extends Hash +{ + /** + * Alphabet used in itoa64 conversions. + * + * @var string + * @since 0.1.0 + */ + protected string $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + $randomState = \microtime(); + if (\function_exists('getmypid')) { + $randomState .= getmypid(); + } + + return ['iteration_count_log2' => 8, 'portable_hashes' => false, 'random_state' => $randomState]; + } + + /** + * @param string $password Input password to hash + * + * @return string hash + */ + public function hash(string $password): string + { + $options = $this->getDefaultOptions(); + + $random = ''; + if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) { + $random = $this->getRandomBytes(16, $options); + $hash = crypt($password, $this->gensaltBlowfish($random, $options)); + if (strlen($hash) === 60) { + return $hash; + } + } + if (strlen($random) < 6) { + $random = $this->getRandomBytes(6, $options); + } + $hash = $this->cryptPrivate($password, $this->gensaltPrivate($random, $options)); + if (strlen($hash) === 34) { + return $hash; + } + + /** + * Returning '*' on error is safe here, but would _not_ be safe + * in a crypt(3)-like function used _both_ for generating new + * hashes and for validating passwords against existing hashes. + */ + return '*'; + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + $verificationHash = $this->cryptPrivate($password, $hash); + if ($verificationHash[0] === '*') { + $verificationHash = crypt($password, $hash); + } + + /** + * This is not constant-time. In order to keep the code simple, + * for timing safety we currently rely on the salts being + * unpredictable, which they are at least in the non-fallback + * cases (that is, when we use /dev/urandom and bcrypt). + */ + return $hash === $verificationHash; + } + + /** + * @param int $count + * + * @return String $output + * @since 0.1.0 + * @throws Exception Thows an Exception if the $count parameter is not a positive integer. + */ + protected function getRandomBytes(int $count, array $options): string + { + if (!is_int($count) || $count < 1) { + throw new \Exception('Argument count must be a positive integer'); + } + $output = ''; + if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } + + if (strlen($output) < $count) { + $output = ''; + + for ($i = 0; $i < $count; $i += 16) { + $options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']); + $output .= md5($options['iteration_count_log2'], true); + } + + $output = substr($output, 0, $count); + } + + return $output; + } + + /** + * @param String $input + * @param int $count + * + * @return String $output + * @since 0.1.0 + * @throws Exception Thows an Exception if the $count parameter is not a positive integer. + */ + protected function encode64($input, $count) + { + if (!is_int($count) || $count < 1) { + throw new \Exception('Argument count must be a positive integer'); + } + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) { + $value |= ord($input[$i]) << 8; + } + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) { + break; + } + if ($i < $count) { + $value |= ord($input[$i]) << 16; + } + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) { + break; + } + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + /** + * @param String $input + * + * @return String $output + * @since 0.1.0 + */ + private function gensaltPrivate($input, $options) + { + $output = '$P$'; + $output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); + + return $output; + } + + /** + * @param String $password + * @param String $setting + * + * @return String $output + * @since 0.1.0 + */ + private function cryptPrivate($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) === $output) { + $output = '*1'; + } + $id = substr($setting, 0, 3); + // We use "$P$", phpBB3 uses "$H$" for the same thing + if ($id !== '$P$' && $id !== '$H$') { + return $output; + } + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) { + return $output; + } + $count = 1 << $count_log2; + $salt = substr($setting, 4, 8); + if (strlen($salt) !== 8) { + return $output; + } + /** + * We were kind of forced to use MD5 here since it's the only + * cryptographic primitive that was available in all versions of PHP + * in use. To implement our own low-level crypto in PHP + * would have result in much worse performance and + * consequently in lower iteration counts and hashes that are + * quicker to crack (by non-PHP code). + */ + $hash = md5($salt . $password, true); + do { + $hash = md5($hash . $password, true); + } while (--$count); + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); + + return $output; + } + + /** + * @param String $input + * + * @return String $output + * @since 0.1.0 + */ + private function gensaltBlowfish($input, $options) + { + /** + * This one needs to use a different order of characters and a + * different encoding scheme from the one in encode64() above. + * We care because the last character in our encoded string will + * only represent 2 bits. While two known implementations of + * bcrypt will happily accept and correct a salt string which + * has the 4 unused bits set to non-zero, we do not want to take + * chances and we also do not want to waste an additional byte + * of entropy. + */ + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $output = '$2a$'; + $output .= chr(ord('0') + intval($options['iteration_count_log2'] / 10)); + $output .= chr(ord('0') + $options['iteration_count_log2'] % 10); + $output .= '$'; + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } +} diff --git a/src/Appwrite/Auth/Hash/Scrypt.php b/src/Appwrite/Auth/Hash/Scrypt.php new file mode 100644 index 0000000000..821b1fba69 --- /dev/null +++ b/src/Appwrite/Auth/Hash/Scrypt.php @@ -0,0 +1,51 @@ +getOptions(); + + return \scrypt($password, $options['salt'], $options['costCpu'], $options['costMemory'], $options['costParallel'], $options['length']); + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + return $hash === $this->hash($password); + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return [ 'costCpu' => 8, 'costMemory' => 14, 'costParallel' => 1, 'length' => 64 ]; + } +} diff --git a/src/Appwrite/Auth/Hash/Scryptmodified.php b/src/Appwrite/Auth/Hash/Scryptmodified.php new file mode 100644 index 0000000000..7717f324e5 --- /dev/null +++ b/src/Appwrite/Auth/Hash/Scryptmodified.php @@ -0,0 +1,80 @@ +getOptions(); + + $derivedKeyBytes = $this->generateDerivedKey($password); + $signerKeyBytes = \base64_decode($options['signerKey']); + + $hashedPassword = $this->hashKeys($signerKeyBytes, $derivedKeyBytes); + + return \base64_encode($hashedPassword); + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + return $this->hash($password) === $hash; + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return [ ]; + } + + private function generateDerivedKey(string $password) + { + $options = $this->getOptions(); + + $saltBytes = \base64_decode($options['salt']); + $saltSeparatorBytes = \base64_decode($options['saltSeparator']); + + $password = mb_convert_encoding($password, 'UTF-8'); + $derivedKey = \scrypt($password, $saltBytes . $saltSeparatorBytes, 16384, 8, 1, 64); + $derivedKey = \hex2bin($derivedKey); + + return $derivedKey; + } + + private function hashKeys($signerKeyBytes, $derivedKeyBytes): string + { + $key = \substr($derivedKeyBytes, 0, 32); + + $iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + + $hash = \openssl_encrypt($signerKeyBytes, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv); + + return $hash; + } +} diff --git a/src/Appwrite/Auth/Hash/Sha.php b/src/Appwrite/Auth/Hash/Sha.php new file mode 100644 index 0000000000..c2ae3b52c1 --- /dev/null +++ b/src/Appwrite/Auth/Hash/Sha.php @@ -0,0 +1,50 @@ +getOptions()['version']; + + return \hash($algo, $password); + } + + /** + * @param string $password Input password to validate + * @param string $hash Hash to verify password against + * + * @return boolean true if password matches hash + */ + public function verify(string $password, string $hash): bool + { + return $this->hash($password) === $hash; + } + + /** + * Get default options for specific hashing algo + * + * @return array options named array + */ + public function getDefaultOptions(): array + { + return [ 'version' => 'sha3-512' ]; + } +} diff --git a/src/Appwrite/Auth/Key.php b/src/Appwrite/Auth/Key.php index 09493c802f..44a75a6ee3 100644 --- a/src/Appwrite/Auth/Key.php +++ b/src/Appwrite/Auth/Key.php @@ -110,16 +110,16 @@ class Key $secret = $key; } - $role = USER_ROLE_APPS; + $role = Auth::USER_ROLE_APPS; $roles = Config::getParam('roles', []); - $scopes = $roles[USER_ROLE_APPS]['scopes'] ?? []; + $scopes = $roles[Auth::USER_ROLE_APPS]['scopes'] ?? []; $expired = false; $guestKey = new Key( $project->getId(), $type, - USER_ROLE_GUESTS, - $roles[USER_ROLE_GUESTS]['scopes'] ?? [], + Auth::USER_ROLE_GUESTS, + $roles[Auth::USER_ROLE_GUESTS]['scopes'] ?? [], 'UNKNOWN' ); diff --git a/src/Appwrite/Auth/MFA/Type.php b/src/Appwrite/Auth/MFA/Type.php index d1e267965a..3516ec3780 100644 --- a/src/Appwrite/Auth/MFA/Type.php +++ b/src/Appwrite/Auth/MFA/Type.php @@ -2,8 +2,8 @@ namespace Appwrite\Auth\MFA; +use Appwrite\Auth\Auth; use OTPHP\OTP; -use Utopia\Auth\Proofs\Token; abstract class Type { @@ -51,10 +51,9 @@ abstract class Type public static function generateBackupCodes(int $length = 10, int $total = 6): array { $backups = []; - $token = new Token($length); for ($i = 0; $i < $total; $i++) { - $backups[] = $token->generate(); + $backups[] = Auth::tokenGenerator($length); } return $backups; diff --git a/src/Appwrite/Auth/Validator/PasswordHistory.php b/src/Appwrite/Auth/Validator/PasswordHistory.php index 9b40b6a794..f623ca180d 100644 --- a/src/Appwrite/Auth/Validator/PasswordHistory.php +++ b/src/Appwrite/Auth/Validator/PasswordHistory.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Auth\Hash; +use Appwrite\Auth\Auth; /** * Password. @@ -12,14 +12,16 @@ use Utopia\Auth\Hash; class PasswordHistory extends Password { protected array $history; - protected Hash $hash; + protected string $algo; + protected array $algoOptions; - public function __construct(array $history, Hash $hash) + public function __construct(array $history, string $algo, array $algoOptions = []) { parent::__construct(); $this->history = $history; - $this->hash = $hash; + $this->algo = $algo; + $this->algoOptions = $algoOptions; } /** @@ -44,7 +46,7 @@ class PasswordHistory extends Password public function isValid($value): bool { foreach ($this->history as $hash) { - if (!empty($hash) && $this->hash->verify($value, $hash)) { + if (!empty($hash) && Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) { return false; } } diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 061ace31d7..9d72af9563 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -2,6 +2,7 @@ namespace Appwrite\Migration\Version; +use Appwrite\Auth\Auth; use Appwrite\Migration\Migration; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -117,7 +118,7 @@ class V16 extends Migration * Set default authDuration */ $document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [ - 'duration' => TOKEN_EXPIRATION_LOGIN_LONG + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG ])); /** diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index 79e2a8377d..fbbd4bfde0 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -2,8 +2,8 @@ namespace Appwrite\Migration\Version; +use Appwrite\Auth\Auth; use Appwrite\Migration\Migration; -use Utopia\Auth\Proofs\Password; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; @@ -270,7 +270,7 @@ class V17 extends Migration * Set hashOptions type */ $document->setAttribute('hashOptions', array_merge($document->getAttribute('hashOptions', []), [ - 'type' => $document->getAttribute('hash', (new Password())->getHash()->getName()) + 'type' => $document->getAttribute('hash', Auth::DEFAULT_ALGO) ])); break; } diff --git a/src/Appwrite/Migration/Version/V20.php b/src/Appwrite/Migration/Version/V20.php index 10e2706d0e..9ff041eb33 100644 --- a/src/Appwrite/Migration/Version/V20.php +++ b/src/Appwrite/Migration/Version/V20.php @@ -2,6 +2,7 @@ namespace Appwrite\Migration\Version; +use Appwrite\Auth\Auth; use Appwrite\Migration\Migration; use Exception; use PDOException; @@ -631,15 +632,15 @@ class V20 extends Migration } break; case 'sessions': - $duration = $this->project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $this->project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::addSeconds(new \DateTime(), $duration); $document->setAttribute('expire', $expire); $factors = match ($document->getAttribute('provider')) { - SESSION_PROVIDER_EMAIL => ['password'], - SESSION_PROVIDER_PHONE => ['phone'], - SESSION_PROVIDER_ANONYMOUS => ['anonymous'], - SESSION_PROVIDER_TOKEN => ['token'], + Auth::SESSION_PROVIDER_EMAIL => ['password'], + Auth::SESSION_PROVIDER_PHONE => ['phone'], + Auth::SESSION_PROVIDER_ANONYMOUS => ['anonymous'], + Auth::SESSION_PROVIDER_TOKEN => ['token'], default => ['email'], }; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index b12c32cb23..69af3b7d04 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -18,8 +18,6 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Executor\Executor; use MaxMind\Db\Reader; -use Utopia\Auth\Proofs\Token; -use Utopia\Auth\Store; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -94,8 +92,6 @@ class Create extends Base ->inject('queueForStatsUsage') ->inject('queueForFunctions') ->inject('geodb') - ->inject('store') - ->inject('proofForToken') ->inject('executor') ->callback($this->action(...)); } @@ -118,8 +114,6 @@ class Create extends Base StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, - Store $store, - Token $proofForToken, Executor $executor ) { $async = \strval($async) === 'true' || \strval($async) === '1'; @@ -204,7 +198,7 @@ class Create extends Base foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // Find most recent active session for user ID and JWT headers + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $current = $session; } } diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index b210a020b9..c3b4e33593 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -2,11 +2,10 @@ namespace Appwrite\Platform\Tasks; +use Appwrite\Auth\Auth; use Appwrite\Docker\Compose; use Appwrite\Docker\Env; use Appwrite\Utopia\View; -use Utopia\Auth\Proofs\Password; -use Utopia\Auth\Proofs\Token; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Platform\Action; @@ -150,8 +149,6 @@ class Install extends Action $input = []; - $password = new Password(); - $token = new Token(); foreach ($vars as $var) { if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) { if ($data && $var['default'] !== null) { @@ -160,12 +157,12 @@ class Install extends Action } if ($var['filter'] === 'token') { - $input[$var['name']] = $token->generate(); + $input[$var['name']] = Auth::tokenGenerator(); continue; } if ($var['filter'] === 'password') { - $input[$var['name']] = $password->generate(); + $input[$var['name']] = Auth::passwordGenerator(); continue; } } diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index be542e7811..a88e2e641f 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Workers; +use Appwrite\Auth\Auth; use Exception; use Throwable; use Utopia\Audit\Audit; @@ -84,7 +85,7 @@ class Audits extends Action $userName = $user->getAttribute('name', ''); $userEmail = $user->getAttribute('email', ''); - $userType = $user->getAttribute('type', ACTIVITY_TYPE_USER); + $userType = $user->getAttribute('type', Auth::ACTIVITY_TYPE_USER); // Create event data $eventData = [ diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 1c146a335e..331a2668a3 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Workers; +use Appwrite\Auth\Auth; use Appwrite\Certificates\Adapter as CertificatesAdapter; use Appwrite\Deletes\Identities; use Appwrite\Deletes\Targets; @@ -707,7 +708,7 @@ class Deletes extends Action private function deleteExpiredSessions(Document $project, callable $getProjectDB): void { $dbForProject = $getProjectDB($project); - $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; + $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expired = DateTime::addSeconds(new \DateTime(), -1 * $duration); // Delete Sessions diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 8ee3a2bdb6..abe67e7e86 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -105,7 +105,7 @@ class Project extends Model ->addRule('authDuration', [ 'type' => self::TYPE_INTEGER, 'description' => 'Session duration in seconds.', - 'default' => TOKEN_EXPIRATION_LOGIN_LONG, + 'default' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'example' => 60, ]) ->addRule('authLimit', [ @@ -372,7 +372,7 @@ class Project extends Model $auth = Config::getParam('auth', []); $document->setAttribute('authLimit', $authValues['limit'] ?? 0); - $document->setAttribute('authDuration', $authValues['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG); + $document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG); $document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); $document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0); $document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false); diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 9ebc3f03cf..c2b4896814 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -169,7 +169,6 @@ trait ProjectCustom $project = [ '$id' => $project['body']['$id'], 'name' => $project['body']['name'], - 'teamId' => $team['body']['$id'], 'apiKey' => $key['body']['secret'], 'devKey' => $devKey['body']['secret'], 'webhookId' => $webhook['body']['$id'], diff --git a/tests/e2e/Services/Account/AccountConsoleClientTest.php b/tests/e2e/Services/Account/AccountConsoleClientTest.php index 51de5731bd..1df9ef6c18 100644 --- a/tests/e2e/Services/Account/AccountConsoleClientTest.php +++ b/tests/e2e/Services/Account/AccountConsoleClientTest.php @@ -45,6 +45,7 @@ class AccountConsoleClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 201); $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; + // create team $team = $this->client->call(Client::METHOD_POST, '/teams', [ 'origin' => 'http://localhost', @@ -55,7 +56,6 @@ class AccountConsoleClientTest extends Scope 'teamId' => 'unique()', 'name' => 'myteam' ]); - $this->assertEquals($team['headers']['status-code'], 201); $teamId = $team['body']['$id']; diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 91dce5c09c..4e479344d3 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Projects; +use Appwrite\Auth\Auth; use Appwrite\Extend\Exception; use Appwrite\Tests\Async; use Tests\E2E\Client; @@ -865,7 +866,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year + $this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year /** * Test for SUCCESS @@ -1008,7 +1009,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'duration' => TOKEN_EXPIRATION_LOGIN_LONG, + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -1021,7 +1022,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year + $this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year return ['projectId' => $projectId]; } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 7d69dc7f3e..705da42879 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -4,7 +4,6 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; use PHPUnit\Framework\TestCase; -use Utopia\Auth\Proofs\Token; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -23,25 +22,203 @@ class AuthTest extends TestCase Authorization::setRole(Role::any()->toString()); } + public function testCookieName(): void + { + $name = 'cookie-name'; + + $this->assertEquals(Auth::setCookieName($name), $name); + $this->assertEquals(Auth::$cookieName, $name); + } + + public function testEncodeDecodeSession(): void + { + $id = 'id'; + $secret = 'secret'; + $session = 'eyJpZCI6ImlkIiwic2VjcmV0Ijoic2VjcmV0In0='; + + $this->assertEquals(Auth::encodeSession($id, $secret), $session); + $this->assertEquals(Auth::decodeSession($session), ['id' => $id, 'secret' => $secret]); + } + + public function testHash(): void + { + $secret = 'secret'; + $this->assertEquals(Auth::hash($secret), '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b'); + } + + public function testPassword(): void + { + /* + General tests, using pre-defined hashes generated by online tools + */ + + // Bcrypt - Version Y + $plain = 'secret'; + $hash = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy'; + $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); + + // Bcrypt - Version A + $plain = 'test123'; + $hash = '$2a$12$3f2ZaARQ1AmhtQWx2nmQpuXcWfTj1YV2/Hl54e8uKxIzJe3IfwLiu'; + $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); + + // Bcrypt - Cost 5 + $plain = 'hello-world'; + $hash = '$2a$05$IjrtSz6SN7UJ6Sh3l.b5jODEvEG2LMJTPAHIaLWRvlWx7if3VMkFO'; + $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); + + // Bcrypt - Cost 15 + $plain = 'super-secret-password'; + $hash = '$2a$15$DS0ZzbsFZYumH/E4Qj5oeOHnBcM3nCCsCA2m4Goigat/0iMVQC4Na'; + $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); + + // MD5 - Short + $plain = 'appwrite'; + $hash = '144fa7eaa4904e8ee120651997f70dcc'; + $generatedHash = Auth::passwordHash($plain, 'md5'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); + + // MD5 - Long + $plain = 'AppwriteIsAwesomeBackendAsAServiceThatIsAlsoOpenSourced'; + $hash = '8410e96cf7ac64e0b84c3f8517a82616'; + $generatedHash = Auth::passwordHash($plain, 'md5'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5')); + + // PHPass + $plain = 'pass123'; + $hash = '$P$BVKPmJBZuLch27D4oiMRTEykGLQ9tX0'; + $generatedHash = Auth::passwordHash($plain, 'phpass'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); + + // SHA + $plain = 'developersAreAwesome!'; + $hash = '2455118438cb125354b89bb5888346e9bd23355462c40df393fab514bf2220b5a08e4e2d7b85d7327595a450d0ac965cc6661152a46a157c66d681bed20a4735'; + $generatedHash = Auth::passwordHash($plain, 'sha'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'sha')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'sha')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'sha')); + + // Argon2 + $plain = 'safe-argon-password'; + $hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8'; + $generatedHash = Auth::passwordHash($plain, 'argon2'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'argon2')); + + // Scrypt + $plain = 'some-scrypt-password'; + $hash = 'b448ad7ba88b653b5b56b8053a06806724932d0751988bc9cd0ef7ff059e8ba8a020e1913b7069a650d3f99a1559aba0221f2c277826919513a054e76e339028'; + $generatedHash = Auth::passwordHash($plain, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]); + + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); + $this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-wrong-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); + $this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 10, 'costParallel' => 2])); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2])); + + // ScryptModified tested are in provider-specific tests below + + /* + Provider-specific tests, ensuring functionality of specific use-cases + */ + + // Provider #1 (Database) + $plain = 'example-password'; + $hash = '$2a$10$3bIGRWUes86CICsuchGLj.e.BqdCdg2/1Ud9LvBhJr0j7Dze8PBdS'; + $generatedHash = Auth::passwordHash($plain, 'bcrypt'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt')); + + // Provider #2 (Blog) + $plain = 'your-password'; + $hash = '$P$BkiNDJTpAWXtpaMhEUhUdrv7M0I1g6.'; + $generatedHash = Auth::passwordHash($plain, 'phpass'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass')); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass')); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass')); + + // Provider #2 (Google) + $plain = 'users-password'; + $hash = 'EPKgfALpS9Tvgr/y1ki7ubY4AEGJeWL3teakrnmOacN4XGiyD00lkzEHgqCQ71wGxoi/zb7Y9a4orOtvMV3/Jw=='; + $salt = '56dFqW+kswqktw=='; + $saltSeparator = 'Bw=='; + $signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ=='; + + $options = [ 'salt' => $salt, 'saltSeparator' => $saltSeparator, 'signerKey' => $signerKey ]; + $generatedHash = Auth::passwordHash($plain, 'scryptMod', $options); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scryptMod', $options)); + $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scryptMod', $options)); + $this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scryptMod', $options)); + } + + public function testUnknownAlgo() + { + $this->expectExceptionMessage('Hashing algorithm \'md8\' is not supported.'); + + // Bcrypt - Cost 5 + $plain = 'whatIsMd8?!?'; + $generatedHash = Auth::passwordHash($plain, 'md8'); + $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8')); + } + + public function testPasswordGenerator(): void + { + $this->assertEquals(\mb_strlen(Auth::passwordGenerator()), 40); + $this->assertEquals(\mb_strlen(Auth::passwordGenerator(5)), 10); + } + + public function testTokenGenerator(): void + { + $this->assertEquals(\strlen(Auth::tokenGenerator()), 256); + $this->assertEquals(\strlen(Auth::tokenGenerator(5)), 5); + } + + public function testCodeGenerator(): void + { + $this->assertEquals(6, \strlen(Auth::codeGenerator())); + $this->assertEquals(\mb_strlen(Auth::codeGenerator(256)), 256); + $this->assertEquals(\mb_strlen(Auth::codeGenerator(10)), 10); + $this->assertTrue(is_numeric(Auth::codeGenerator(5))); + } + public function testSessionVerify(): void { - $proofForToken = new Token(); $expireTime1 = 60 * 60 * 24; $secret = 'secret1'; - $hash = $proofForToken->hash($secret); + $hash = Auth::hash($secret); $tokens1 = [ new Document([ '$id' => ID::custom('token1'), 'secret' => $hash, - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1), ]), new Document([ '$id' => ID::custom('token2'), 'secret' => 'secret2', - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1), ]), @@ -53,40 +230,39 @@ class AuthTest extends TestCase new Document([ // Correct secret and type time, wrong expire time '$id' => ID::custom('token1'), 'secret' => $hash, - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2), ]), new Document([ '$id' => ID::custom('token2'), 'secret' => 'secret2', - 'provider' => SESSION_PROVIDER_EMAIL, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => 'test@example.com', 'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2), ]), ]; - $this->assertEquals(Auth::sessionVerify($tokens1, $secret, $proofForToken), 'token1'); - $this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret', $proofForToken), false); - $this->assertEquals(Auth::sessionVerify($tokens2, $secret, $proofForToken), false); - $this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret', $proofForToken), false); + $this->assertEquals(Auth::sessionVerify($tokens1, $secret), 'token1'); + $this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret'), false); + $this->assertEquals(Auth::sessionVerify($tokens2, $secret), false); + $this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret'), false); } public function testTokenVerify(): void { - $proofForToken = new Token(); $secret = 'secret1'; - $hash = $proofForToken->hash($secret); + $hash = Auth::hash($secret); $tokens1 = [ new Document([ '$id' => ID::custom('token1'), - 'type' => TOKEN_TYPE_RECOVERY, + 'type' => Auth::TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => TOKEN_TYPE_RECOVERY, + 'type' => Auth::TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), @@ -95,13 +271,13 @@ class AuthTest extends TestCase $tokens2 = [ new Document([ // Correct secret and type time, wrong expire time '$id' => ID::custom('token1'), - 'type' => TOKEN_TYPE_RECOVERY, + 'type' => Auth::TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => TOKEN_TYPE_RECOVERY, + 'type' => Auth::TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), @@ -110,25 +286,25 @@ class AuthTest extends TestCase $tokens3 = [ // Correct secret and expire time, wrong type new Document([ '$id' => ID::custom('token1'), - 'type' => TOKEN_TYPE_INVITE, + 'type' => Auth::TOKEN_TYPE_INVITE, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)), 'secret' => $hash, ]), new Document([ '$id' => ID::custom('token2'), - 'type' => TOKEN_TYPE_RECOVERY, + 'type' => Auth::TOKEN_TYPE_RECOVERY, 'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)), 'secret' => 'secret2', ]), ]; - $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), $tokens1[0]); - $this->assertEquals(Auth::tokenVerify($tokens1, null, $secret, $proofForToken), $tokens1[0]); - $this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); - $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false); - $this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); - $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false); - $this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false); + $this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, $secret), $tokens1[0]); + $this->assertEquals(Auth::tokenVerify($tokens1, null, $secret), $tokens1[0]); + $this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, $secret), false); + $this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); + $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, $secret), false); + $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false); } public function testIsPrivilegedUser(): void @@ -136,16 +312,16 @@ class AuthTest extends TestCase $this->assertEquals(false, Auth::isPrivilegedUser([])); $this->assertEquals(false, Auth::isPrivilegedUser([Role::guests()->toString()])); $this->assertEquals(false, Auth::isPrivilegedUser([Role::users()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_ADMIN])); - $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_DEVELOPER])); - $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER])); - $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_SYSTEM])); + $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_ADMIN])); + $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_DEVELOPER])); + $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER])); + $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_SYSTEM])); - $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, Role::guests()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, Role::guests()->toString()])); - $this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER])); + $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Role::guests()->toString()])); + $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()])); + $this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER])); } public function testIsAppUser(): void @@ -153,16 +329,16 @@ class AuthTest extends TestCase $this->assertEquals(false, Auth::isAppUser([])); $this->assertEquals(false, Auth::isAppUser([Role::guests()->toString()])); $this->assertEquals(false, Auth::isAppUser([Role::users()->toString()])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_ADMIN])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_DEVELOPER])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER])); - $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_SYSTEM])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_ADMIN])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_DEVELOPER])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER])); + $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_SYSTEM])); - $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, USER_ROLE_APPS])); - $this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, Role::guests()->toString()])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, Role::guests()->toString()])); - $this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER])); + $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS])); + $this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Role::guests()->toString()])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()])); + $this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER])); } public function testGuestRoles(): void @@ -242,7 +418,7 @@ class AuthTest extends TestCase public function testPrivilegedUserRoles(): void { - Authorization::setRole(USER_ROLE_OWNER); + Authorization::setRole(Auth::USER_ROLE_OWNER); $user = new Document([ '$id' => ID::custom('123'), 'emailVerification' => true, @@ -286,7 +462,7 @@ class AuthTest extends TestCase public function testAppUserRoles(): void { - Authorization::setRole(USER_ROLE_APPS); + Authorization::setRole(Auth::USER_ROLE_APPS); $user = new Document([ '$id' => ID::custom('123'), 'memberships' => [ diff --git a/tests/unit/Auth/KeyTest.php b/tests/unit/Auth/KeyTest.php index 56232dcc6b..8ae2114697 100644 --- a/tests/unit/Auth/KeyTest.php +++ b/tests/unit/Auth/KeyTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit\Auth; use Ahc\Jwt\JWT; +use Appwrite\Auth\Auth; use Appwrite\Auth\Key; use PHPUnit\Framework\TestCase; use Utopia\Config\Config; @@ -20,7 +21,7 @@ class KeyTest extends TestCase 'collections.read', 'documents.read', ]; - $roleScopes = Config::getParam('roles', [])[USER_ROLE_APPS]['scopes']; + $roleScopes = Config::getParam('roles', [])[Auth::USER_ROLE_APPS]['scopes']; $key = static::generateKey($projectId, $usage, $scopes); $project = new Document(['$id' => $projectId,]); @@ -28,7 +29,7 @@ class KeyTest extends TestCase $this->assertEquals($projectId, $decoded->getProjectId()); $this->assertEquals(API_KEY_DYNAMIC, $decoded->getType()); - $this->assertEquals(USER_ROLE_APPS, $decoded->getRole()); + $this->assertEquals(Auth::USER_ROLE_APPS, $decoded->getRole()); $this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes()); } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index 536228b504..8ba0374093 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -59,7 +59,7 @@ class MessagingChannelsTest extends TestCase 'confirm' => true, 'roles' => [ empty($index % 2) - ? USER_ROLE_ADMIN + ? Auth::USER_ROLE_ADMIN : 'member', ] ] @@ -294,7 +294,7 @@ class MessagingChannelsTest extends TestCase } $role = empty($index % 2) - ? USER_ROLE_ADMIN + ? Auth::USER_ROLE_ADMIN : 'member'; $permissions = [ diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php index 2dc47b9b2b..bb6c49d2fc 100644 --- a/tests/unit/Migration/MigrationTest.php +++ b/tests/unit/Migration/MigrationTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use ReflectionMethod; use Utopia\Database\Document; -class MigrationTest extends TestCase +abstract class MigrationTest extends TestCase { /** * @var Migration From f4efb81832d35c3d81c0db80e63f3e223f01025f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 15:07:53 +1300 Subject: [PATCH 369/385] Update lock --- composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index f2efa0d785..0dfe37ce9b 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": "568800edca746c4e8d0d50648b25f589", + "content-hash": "407c1717bfef580d733ff2bbb232ec8a", "packages": [ { "name": "adhocore/jwt", @@ -3792,16 +3792,16 @@ }, { "name": "utopia-php/database", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "da0d583e1590e37515edfa338d8684c01833455f" + "reference": "b92554e2e7b3b00f0f0acb2b53c6a11e1349b81e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/da0d583e1590e37515edfa338d8684c01833455f", - "reference": "da0d583e1590e37515edfa338d8684c01833455f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b92554e2e7b3b00f0f0acb2b53c6a11e1349b81e", + "reference": "b92554e2e7b3b00f0f0acb2b53c6a11e1349b81e", "shasum": "" }, "require": { @@ -3811,7 +3811,7 @@ "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.10.*", + "utopia-php/mongo": "0.11.*", "utopia-php/pools": "0.8.*" }, "require-dev": { @@ -3844,9 +3844,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/3.0.0" + "source": "https://github.com/utopia-php/database/tree/3.0.2" }, - "time": "2025-10-20T05:51:31+00:00" + "time": "2025-10-20T23:58:56+00:00" }, { "name": "utopia-php/detector", @@ -4402,16 +4402,16 @@ }, { "name": "utopia-php/mongo", - "version": "0.10.0", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" + "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/34bc0cda8ea368cde68702a6fffe2c3ac625398e", + "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e", "shasum": "" }, "require": { @@ -4457,9 +4457,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.10.0" + "source": "https://github.com/utopia-php/mongo/tree/0.11.0" }, - "time": "2025-10-02T04:50:07+00:00" + "time": "2025-10-20T11:11:23+00:00" }, { "name": "utopia-php/orchestration", From 00615c2f38cfc07fa526f40c5ca009d6adc7d657 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 28 Aug 2025 14:30:13 -0700 Subject: [PATCH 370/385] chore: create migration version for 1.8.x --- src/Appwrite/Migration/Version/V23.php | 112 ++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php index d5caf2ab3c..fb9a646898 100644 --- a/src/Appwrite/Migration/Version/V23.php +++ b/src/Appwrite/Migration/Version/V23.php @@ -8,6 +8,9 @@ use Throwable; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Conflict; +use Utopia\Database\Exception\Structure; +use Utopia\Database\Exception\Timeout; class V23 extends Migration { @@ -19,34 +22,117 @@ class V23 extends Migration /** * Disable SubQueries for Performance. */ - foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) { + $subQueries = [ + 'subQueryAttributes', + 'subQueryAuthenticators', + 'subQueryChallenges', + 'subQueryDevKeys', + 'subQueryIndexes', + 'subQueryKeys', + 'subQueryMemberships', + 'subQueryPlatforms', + 'subQueryProjectVariables', + 'subQuerySessions', + 'subQueryTargets', + 'subQueryTokens', + 'subQueryTopicTargets', + 'subQueryVariables', + 'subQueryWebhooks', + ]; + foreach ($subQueries as $name) { Database::addFilter( $name, - fn () => null, - fn () => [] + fn() => null, + fn() => [] ); } - Console::info('Migrating databases'); - $this->migrateDatabases(); + Console::info('Migrating collections'); + $this->migrateCollections(); + + Console::info('Migrating documents'); + $this->forEachDocument($this->migrateDocument(...)); } /** - * Migrate Databases. + * Migrate Collections. * * @return void * @throws Exception|Throwable */ - private function migrateDatabases(): void + private function migrateCollections(): void { - if ($this->project->getId() === 'console') { - return; + $projectInternalId = $this->project->getSequence(); + + if (empty($projectInternalId)) { + throw new Exception('Project ID is null'); } - // since required + default can't be used together - // so first creating the attribute then bulk updating the attribute - $this->createAttributeFromCollection($this->dbForProject, 'databases', 'type'); - $this->dbForProject->updateDocuments('databases', new Document(['type' => 'legacy'])); + $collectionType = match ($projectInternalId) { + 'console' => 'console', + default => 'projects', + }; + + $collections = $this->collections[$collectionType]; + + foreach ($collections as $collection) { + $id = $collection['$id']; + + if (empty($id)) { + continue; + } + + Console::log("Migrating collection \"{$id}\""); + + switch ($id) { + case 'databases': + $attributes = [ + 'type', + ]; + try { + $this->createAttributesFromCollection($this->dbForProject, $id, $attributes); + } catch (\Throwable $th) { + Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}"); + } + $this->dbForProject->purgeCachedCollection($id); + break; + case 'schedules': + try { + $this->dbForProject->updateAttribute($id, 'resourceInternalId', required: false); + } catch (Throwable $th) { + Console::warning("'resourceInternalId' from {$id}: {$th->getMessage()}"); + } + break; + + $this->dbForProject->purgeCachedCollection($id); + break; + default: + break; + } + } } + /** + * Fix run on each document + * + * @param Document $document + * @return Document + * @throws Conflict + * @throws Structure + * @throws Timeout + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Authorization + * @throws \Utopia\Database\Exception\Query + */ + private function migrateDocument(Document $document): Document + { + switch ($document->getCollection()) { + case 'databases': + $document->setAttribute('type', $document->getAttribute('type', 'legacy')); + break; + default: + break; + } + return $document; + } } From 59f82a0dd74204c706902b4ba9e506ff15b5a136 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Mon, 15 Sep 2025 11:39:25 -0700 Subject: [PATCH 371/385] feat: bump console to version 7.1.11 --- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index ed4de38d2b..d59fe36db6 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -179,7 +179,7 @@ $image = $this->getParam('image', ''); appwrite-console: <<: *x-logging container_name: appwrite-console - image: /console:7.0.2 + image: /console:7.1.11 restart: unless-stopped networks: - appwrite diff --git a/docker-compose.yml b/docker-compose.yml index da6362b4c4..116531e673 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -219,7 +219,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:7.0.2 + image: appwrite/console:7.1.11 restart: unless-stopped networks: - appwrite From a9b18811ea8113b03975c75a8ff90ef71b385445 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Tue, 23 Sep 2025 14:35:01 -0700 Subject: [PATCH 372/385] fix(self-hosted): clear cache for collections and documents In older versions of Appwrite, the internal ID was the $internalId attribute. However, it has now changed to $sequence so that it can align with the publicly exposed attribute for an auto-incrementing ID. The problem with this change is that data in the cache still references the old $internalId attribute, which can lead to inconsistencies and errors when accessing cached documents. To resolve this issue, we need to clear the cache for all collections and documents at the beginning of the migration so that the new $sequence attribute is used. --- src/Appwrite/Migration/Version/V23.php | 58 ++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php index fb9a646898..b3d29fad64 100644 --- a/src/Appwrite/Migration/Version/V23.php +++ b/src/Appwrite/Migration/Version/V23.php @@ -42,14 +42,22 @@ class V23 extends Migration foreach ($subQueries as $name) { Database::addFilter( $name, - fn() => null, - fn() => [] + fn () => null, + fn () => [] ); } Console::info('Migrating collections'); $this->migrateCollections(); + if ($this->project->getSequence() != 'console') { + Console::info('Migrating Databases'); + $this->migrateDatabases(); + } + + Console::info('Migrating Buckets'); + $this->migrateBuckets(); + Console::info('Migrating documents'); $this->forEachDocument($this->migrateDocument(...)); } @@ -84,6 +92,10 @@ class V23 extends Migration Console::log("Migrating collection \"{$id}\""); + // Clear cache to ensure new $sequence is used + $this->dbForProject->purgeCachedCollection($id); + $this->dbForProject->purgeCachedDocument(Database::METADATA, $id); + switch ($id) { case 'databases': $attributes = [ @@ -102,8 +114,6 @@ class V23 extends Migration } catch (Throwable $th) { Console::warning("'resourceInternalId' from {$id}: {$th->getMessage()}"); } - break; - $this->dbForProject->purgeCachedCollection($id); break; default: @@ -112,6 +122,46 @@ class V23 extends Migration } } + /** + * Migrate all Database Table tables + * + * @return void + * @throws Exception + */ + private function migrateDatabases(): void + { + $this->dbForProject->foreach('databases', function (Document $database) { + Console::log("Migrating Collections of {$database->getId()} ({$database->getAttribute('name')})"); + + $databaseTable = "database_{$database->getSequence()}"; + $this->dbForProject->purgeCachedCollection($databaseTable); + + $this->dbForProject->foreach($databaseTable, function (Document $collection) use ($databaseTable) { + Console::log("Migrating Collection of {$collection->getId()} ({$collection->getAttribute('name')})"); + + $collectionTable = "{$databaseTable}_collection_{$collection->getSequence()}"; + $this->dbForProject->purgeCachedCollection($collectionTable); + }); + }); + } + + /** + * Migrate all Bucket tables + * + * @return void + * @throws \Exception + * @throws \PDOException + */ + protected function migrateBuckets(): void + { + $this->dbForProject->foreach('buckets', function (Document $bucket) { + Console::log("Migrating Bucket {$bucket->getId()} ({$bucket->getAttribute('name')})"); + + $bucketTable = "bucket_{$bucket->getSequence()}"; + $this->dbForProject->purgeCachedCollection($bucketTable); + }); + } + /** * Fix run on each document * From 0a809f85f2a7b31967ad1802d747c29c6f184780 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Wed, 1 Oct 2025 17:58:11 -0700 Subject: [PATCH 373/385] fix(self-hosted): create missing project attributes Appwrite 1.6.1 added these attributes, but the migration was never updated to include those new attributes --- src/Appwrite/Migration/Version/V23.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php index b3d29fad64..8a26dbf5f1 100644 --- a/src/Appwrite/Migration/Version/V23.php +++ b/src/Appwrite/Migration/Version/V23.php @@ -97,6 +97,18 @@ class V23 extends Migration $this->dbForProject->purgeCachedDocument(Database::METADATA, $id); switch ($id) { + case 'projects': + $attributes = [ + 'pingCount', + 'pingedAt' + ]; + try { + $this->createAttributesFromCollection($this->dbForProject, $id, $attributes); + } catch (\Throwable $th) { + Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}"); + } + $this->dbForProject->purgeCachedCollection($id); + break; case 'databases': $attributes = [ 'type', From 01765fd27f538682f755f440eb3c73b81e4b8e31 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 9 Oct 2025 16:19:42 -0700 Subject: [PATCH 374/385] feat: bump console to version 7.4.4 --- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index d59fe36db6..a996263f4d 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -179,7 +179,7 @@ $image = $this->getParam('image', ''); appwrite-console: <<: *x-logging container_name: appwrite-console - image: /console:7.1.11 + image: /console:7.4.4 restart: unless-stopped networks: - appwrite diff --git a/docker-compose.yml b/docker-compose.yml index 116531e673..8384e14ac2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -219,7 +219,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:7.1.11 + image: appwrite/console:7.4.4 restart: unless-stopped networks: - appwrite From 97312d1a6ddf8f20cf8dda9f606672802293252d Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 9 Oct 2025 16:54:41 -0700 Subject: [PATCH 375/385] feat(self-hosted): update migration for transactions collections --- src/Appwrite/Migration/Version/V23.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php index 8a26dbf5f1..7a6d58d59f 100644 --- a/src/Appwrite/Migration/Version/V23.php +++ b/src/Appwrite/Migration/Version/V23.php @@ -97,6 +97,10 @@ class V23 extends Migration $this->dbForProject->purgeCachedDocument(Database::METADATA, $id); switch ($id) { + case '_metadata': + $this->createCollection('transactions'); + $this->createCollection('transactionLogs'); + break; case 'projects': $attributes = [ 'pingCount', From 46f249fd3bf4047564fac317a147a8443ee3426f Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Tue, 14 Oct 2025 15:01:33 -0700 Subject: [PATCH 376/385] feat: bump console to version 7.4.7 --- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index a996263f4d..4c98b20b18 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -179,7 +179,7 @@ $image = $this->getParam('image', ''); appwrite-console: <<: *x-logging container_name: appwrite-console - image: /console:7.4.4 + image: /console:7.4.7 restart: unless-stopped networks: - appwrite diff --git a/docker-compose.yml b/docker-compose.yml index 8384e14ac2..b72f12a116 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -219,7 +219,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:7.4.4 + image: appwrite/console:7.4.7 restart: unless-stopped networks: - appwrite From a3f51298fe616dd89a31f7d7567c295f85c3a269 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 28 Aug 2025 14:31:42 -0700 Subject: [PATCH 377/385] chore: update CHANGES.md for 1.8.0 release --- CHANGES.md | 388 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index bc903e4b31..7d40c41aaf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,391 @@ +# Version 1.8.0 + +## What's Changed + +### Notable changes + +* Do not allow full range by @basert in https://github.com/appwrite/appwrite/pull/9847 +* Expose internal id as a part of auto increment id by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9713 +* Expose sequence by @abnegate in https://github.com/appwrite/appwrite/pull/9870 +* Add flutter 3.32 and dart 3.8 runtimes by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9914 +* Shorten commit url and branch url by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9919 +* Remove powered by from error pages by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9927 +* Enable resource limits on GIF previews by @basert in https://github.com/appwrite/appwrite/pull/9940 +* Only run maintenance task for projects accessed in last 24 hours by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9989 +* Add increment + decrement routes by @abnegate in https://github.com/appwrite/appwrite/pull/9986 +* Only run maintenance task for projects accessed in last 30 days by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9995 +* Update appwrite-assistant image version to 0.8.3 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10003 +* Update emails to use button by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9590 +* Create commit & branch url for first git deployment when site is linked to repo by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9969 +* Handle React Native schemes by @loks0n in https://github.com/appwrite/appwrite/pull/9650 +* Handle origin validation for web extensions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10107 +* Preview text for emails by @hmacr in https://github.com/appwrite/appwrite/pull/10198 +* Create email target when using email OTP registration by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10224 +* Add CSV imports by @abnegate in https://github.com/appwrite/appwrite/pull/10231 +* Add support for svg favicons by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10255 +* Realtime support for bulk api by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10096 +* Skip redundant subqueries in users list route by @abnegate in https://github.com/appwrite/appwrite/pull/10297 +* Add native sign in with Apple function template by @adityaoberai in https://github.com/appwrite/appwrite/pull/10286 +* Add support for HEAD requests by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10304 +* Update invite email copy by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10309 +* Increase dynamic API key expiration by @Meldiron in https://github.com/appwrite/appwrite/pull/10328 +* Add TablesDB service by @abnegate in https://github.com/appwrite/appwrite/pull/10333 +* Add execution.deploymentId to response model by @Meldiron in https://github.com/appwrite/appwrite/pull/10357 +* Switch Union China Pay to just Union Pay by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10372 and https://github.com/appwrite/appwrite/pull/10382 +* Add execution id and log id to response headers by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10379 +* Add executionId and client IP to function headers by @JoshiJoshiJoshi in https://github.com/appwrite/appwrite/pull/9147 +* Allow HEAD requests in function executions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10385 +* Add support for select queries when listing deployments by @Meldiron in https://github.com/appwrite/appwrite/pull/10380 +* Add spatial type attributes by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10356 and https://github.com/appwrite/appwrite/pull/10443 +* Add realtime support for bulk upserts by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10425 +* Add previewUrl to vcs comment from vcs controller by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10396 +* Rename verification SDK methods to be more specific by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10606 +* Add project name in email subject by @hmacr in https://github.com/appwrite/appwrite/pull/10609 +* Throw error when email is not available for account verification by @hmacr in https://github.com/appwrite/appwrite/pull/10533 +* Add support for transactions by @abnegate in https://github.com/appwrite/appwrite/pull/10023 and https://github.com/appwrite/appwrite/pull/10624 +* Use bcc only emails for smtp by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10644 + +### Fixes + +* Fix rules on active deployment by @Meldiron in https://github.com/appwrite/appwrite/pull/9902 +* Fix for upserts with differing optional parameter sets by @abnegate in https://github.com/appwrite/appwrite/pull/9928 +* Fix teams deletion by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9888 +* Fix deletion logic by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9938 +* Update database for upsert fix by @abnegate in https://github.com/appwrite/appwrite/pull/9941 +* Fix expire format in account recovery, verification, phone and mfa by @jmastr in https://github.com/appwrite/appwrite/pull/9600 +* Fix github comments and deployment creation on branch deletion by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9949 +* Fix cache issues with proxy for deployment download by @Meldiron in https://github.com/appwrite/appwrite/pull/9971 +* Redirect rule parent resource by @Meldiron in https://github.com/appwrite/appwrite/pull/9982 +* Fix usage queues by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9946 +* Transfer control for the migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9997 +* Prevent 'Attribute "factors" must be an array' error by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10004 +* Fix all vcs urls missing region by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9998 +* Add readable error for csv imports by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9947 +* Fix missing screenshot logs by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10024 +* Update executor to fix s3 endpoint bug by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10036 +* Fix build duration calculation by @Meldiron in https://github.com/appwrite/appwrite/pull/10053 +* Fix logs order by @Meldiron in https://github.com/appwrite/appwrite/pull/10052 +* Fix platform check for Sites with automatic rule by @Meldiron in https://github.com/appwrite/appwrite/pull/10043 +* Increase cache ttl to ensure hits by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10079 +* Fix connect to existing repo flow by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10034 +* Fix migrations path and type by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10090 +* Fix JWT authentication database selection for admin mode by @arielweinberger in https://github.com/appwrite/appwrite/pull/10098 +* Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9999 +* Fix file tokens not working on file-security by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10120 +* Fix build activation race condition by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9952 +* Changed the default permission param of upsert document by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10129 +* Fix success validation in oauth2 redirect by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10130 +* Update OAuth2 redirect URLs by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10119 +* Fix specs with new env vars by @Meldiron in https://github.com/appwrite/appwrite/pull/10135 +* Skip deployment when commit is created by us by @hmacr in https://github.com/appwrite/appwrite/pull/10187 +* Use direct source for file-preview when empty by @hmacr in https://github.com/appwrite/appwrite/pull/10181 +* Better error message for invalid function scheduled time by @hmacr in https://github.com/appwrite/appwrite/pull/10201 +* Add defaultBranch in getRepository response by @hmacr in https://github.com/appwrite/appwrite/pull/10190 +* Filter sequence to int because any models skip rule checks by @abnegate in https://github.com/appwrite/appwrite/pull/10221 +* Fix 500 errors on robots and humans txt files by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10248 +* Fix atomic number ops with limit 0 by @abnegate in https://github.com/appwrite/appwrite/pull/10264 +* Update build command for flutter by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10288 +* Add a fallback locale by @Meldiron in https://github.com/appwrite/appwrite/pull/10307 +* Fix variables sharing across resources by @Meldiron in https://github.com/appwrite/appwrite/pull/10308 +* Fix uncaught invalid arg by @abnegate in https://github.com/appwrite/appwrite/pull/10318 +* Add missing upsert event by @abnegate in https://github.com/appwrite/appwrite/pull/10317 +* Improve font reliability by @Meldiron in https://github.com/appwrite/appwrite/pull/10332 +* Truncate logs in function worker by @samikshaaagarwal in https://github.com/appwrite/appwrite/pull/9773 +* Fix event template configuration issues by @adityaoberai in https://github.com/appwrite/appwrite/pull/10350 +* Fix users events & missed publisher logic for Functions by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10348 +* Fix incorrect file token expiry by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/10329 +* Fix upserting that makes no change by @fogelito in https://github.com/appwrite/appwrite/pull/10363 and https://github.com/appwrite/appwrite/pull/10364 +* Fix domain validator by @Meldiron in https://github.com/appwrite/appwrite/pull/10374 +* Apply sequence integer casting and attribute cleanup fixes to Row model, TablesDB tests, and document processing by @Copilot in https://github.com/appwrite/appwrite/pull/10383 +* Fix domain validator by @abnegate in https://github.com/appwrite/appwrite/pull/10386 +* Fix sequence removal by @abnegate in https://github.com/appwrite/appwrite/pull/10388 +* Fix TablesDB scopes by @abnegate in https://github.com/appwrite/appwrite/pull/10387 +* Fix request filter by @abnegate in https://github.com/appwrite/appwrite/pull/10389 +* Fix nested filter selects by @abnegate in https://github.com/appwrite/appwrite/pull/10393 +* Fix readonly attr stripping on write by @abnegate in https://github.com/appwrite/appwrite/pull/10405 +* Replace %s with mustache placeholder by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10392 +* Support array headers for set-cookie by @Meldiron in https://github.com/appwrite/appwrite/pull/10427 +* Fix put prefs structure validation by @fogelito in https://github.com/appwrite/appwrite/pull/10436 +* Fix oauth identity check by @Meldiron in https://github.com/appwrite/appwrite/pull/10460 +* Fix check by @abnegate in https://github.com/appwrite/appwrite/pull/10489 +* Fix database usage metrics by @Divyansha23 in https://github.com/appwrite/appwrite/pull/10483 +* Throw appropriate 400s from request filters by @abnegate in https://github.com/appwrite/appwrite/pull/10502 +* Catch query exception on bucket/file list by @abnegate in https://github.com/appwrite/appwrite/pull/10505 +* Use outputDirectory attribute from deployment by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/10571 +* Fix buildOutput attribute name in deployment check by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/10572 +* Update database for nested selection fix by @abnegate in https://github.com/appwrite/appwrite/pull/10577 +* Auto-allow sites domain for OAuth by @hmacr in https://github.com/appwrite/appwrite/pull/10503 +* Handle OIDC well-known endpoint errors by @hmacr in https://github.com/appwrite/appwrite/pull/10589 +* Correct invalid template links in Create temporary deployment endpoint by @Priyanshuthapliyal2005 in https://github.com/appwrite/appwrite/pull/10581 +* Update broken create table links in TablesDB docs by @Priyanshuthapliyal2005 in https://github.com/appwrite/appwrite/pull/10592 +* Fix cross API compatibility by @abnegate in https://github.com/appwrite/appwrite/pull/10626 +* Fix code 0 from databases on realtime by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10631 +* Throw duplicate error when function id already exists by @hmacr in https://github.com/appwrite/appwrite/pull/10618 + +### Miscellaneous + +* Fix task coroutine hooks by @basert in https://github.com/appwrite/appwrite/pull/9850 +* Feat sync encrypt updates by @abnegate in https://github.com/appwrite/appwrite/pull/9871 +* Revert "Feat sync encrypt updates" by @abnegate in https://github.com/appwrite/appwrite/pull/9877 +* Add builds worker group by @loks0n in https://github.com/appwrite/appwrite/pull/9872 +* Revert encrypted attribute changes by @abnegate in https://github.com/appwrite/appwrite/pull/9898 +* Update sdk generator and sdks by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9849 +* Release cli by @abnegate in https://github.com/appwrite/appwrite/pull/9900 +* Improve how rules are fetched by @Meldiron in https://github.com/appwrite/appwrite/pull/9915 +* Sync 1.6 by @abnegate in https://github.com/appwrite/appwrite/pull/9920 +* Update messaging library by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9764 +* Disable TCP hook on stats resources by @abnegate in https://github.com/appwrite/appwrite/pull/9932 +* Remove JSON index on roles due to MySQL bug by @fogelito in https://github.com/appwrite/appwrite/pull/9924 +* Update queue by @abnegate in https://github.com/appwrite/appwrite/pull/9936 +* Fix flaky account tests by @loks0n in https://github.com/appwrite/appwrite/pull/9954 +* Fix flaky messaging test by @loks0n in https://github.com/appwrite/appwrite/pull/9957 +* Make usage tests robust by @loks0n in https://github.com/appwrite/appwrite/pull/9956 +* Increase deployment timeouts in tests by @loks0n in https://github.com/appwrite/appwrite/pull/9955 +* Graceful shutdown on SIGTERM by @basert in https://github.com/appwrite/appwrite/pull/9890 +* Bring back telemetry for storage by @basert in https://github.com/appwrite/appwrite/pull/9903 +* Update version to 1.7.4 and add experimental warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9959 +* Return queue pre-fetch results by @basert in https://github.com/appwrite/appwrite/pull/9731 +* Update SDK versions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9987 +* Restore unique filename for health check #9842 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9993 +* Add after build hook by @loks0n in https://github.com/appwrite/appwrite/pull/9996 +* Remove endpoint selector by @loks0n in https://github.com/appwrite/appwrite/pull/10000 +* Use static code instead of astro in tests by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9966 +* Add ref param to vcs list contents by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9991 +* Update coderabbit config file by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10005 +* TAR support by @loks0n in https://github.com/appwrite/appwrite/pull/10016 +* Update delete project scope by @shimonewman in https://github.com/appwrite/appwrite/pull/10017 +* Lazy-load relationships by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9669 +* Revert "Feat: Lazy-load relationships" by @abnegate in https://github.com/appwrite/appwrite/pull/10018 +* Revert "Update delete project scope" by @abnegate in https://github.com/appwrite/appwrite/pull/10022 +* 1.8.x by @abnegate in https://github.com/appwrite/appwrite/pull/9985 +* Update cli version and add bulk operation warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10007 +* Update Appwrite description to include Sites, add MCP to products list by @ebenezerdon in https://github.com/appwrite/appwrite/pull/9867 +* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10026 +* Fix duplication of platforms in swagger specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10008 +* Update react native sdk and changelog by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10025 +* Update delete project signature by @shimonewman in https://github.com/appwrite/appwrite/pull/10028 +* Fix Golang SDK examples for docs by @adityaoberai in https://github.com/appwrite/appwrite/pull/10001 +* Revert "worker: Graceful shutdown on SIGTERM" by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10035 +* Fix benchmark CI by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10055 +* Use ->action(...)) instead of ->callback([$this, 'action']); by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9967 +* Override project via custom domains log by @shimonewman in https://github.com/appwrite/appwrite/pull/10011 +* Add database worker job logging by @abnegate in https://github.com/appwrite/appwrite/pull/10056 +* Add runtimeEntrypoint param by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10062 +* Add missing injections by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10061 +* Replace Console loop with Swoole Timer for stats resource m… by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10054 +* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10063 +* Fix parameter order in action function for robots.txt route by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10067 +* Preview endpoint logging by @Meldiron in https://github.com/appwrite/appwrite/pull/10068 +* Fix flakyness of account tests by @Meldiron in https://github.com/appwrite/appwrite/pull/10066 +* Update cli to 8.1.0 and add changelog by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10070 +* Update composer.json and composer.lock to include appwrite-lab… by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10051 +* Fix tests, for `Cloud` by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10085 +* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10084 +* Revert "chore: update composer.json and composer.lock to include appwrite-lab…" by @abnegate in https://github.com/appwrite/appwrite/pull/10086 +* Update README to add Bulk API link by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10095 +* Add redis publisher to schedule base if available by @abnegate in https://github.com/appwrite/appwrite/pull/10099 +* Fix site template test by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10104 +* Update nodejs 17.1.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10088 +* Update README.md to add Upsert announcement by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10112 +* Reduce delete batch size by @fogelito in https://github.com/appwrite/appwrite/pull/10128 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10134 +* Speed up tests by @Meldiron in https://github.com/appwrite/appwrite/pull/10127 +* Update cli to 8.2.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10136 +* Prevent injected $user from being shadowed by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10150 +* Update react native to 0.10.1 and dotnet to 0.14.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10138 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10153 +* Update cli 8.2.1 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10155 +* Fix build usage specification by @loks0n in https://github.com/appwrite/appwrite/pull/10157 +* Handle redirect validator in specs + GraphQL type mapper by @abnegate in https://github.com/appwrite/appwrite/pull/10158 +* Update dart 16.1.0, flutter 17.0.2 and cli 8.2.2 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10161 +* Improve invalid scheme error in origin check by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10164 +* 1.7.x by @Meldiron in https://github.com/appwrite/appwrite/pull/9897 +* Added the cases of null permissions in the upsert route and update th… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10179 +* Fix 1.7.x specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10197 +* Suppress git-action exception in deployment worker by @hmacr in https://github.com/appwrite/appwrite/pull/10199 +* Stats-usage on redis by @loks0n in https://github.com/appwrite/appwrite/pull/10156 +* Fix templates on `1.7.x`. by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10203 +* Change preview & body for MFA email by @hmacr in https://github.com/appwrite/appwrite/pull/10205 +* Add docs for nestedType, encode, from and toMap by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10204 +* Update sdks 1.7.x by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10202 +* Update migration release by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10222 +* Remove sequence on incoming docs by @abnegate in https://github.com/appwrite/appwrite/pull/10228 +* Filter certificates renewal task in maintenance by region by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10227 +* Move changelog to sdks platforms array by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10233 +* Update changelog and sdk gen by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10247 +* Telemetry for cache hits and misses by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10240 +* Add model examples + additonal examples to specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10249 +* Update favicons endpoint to fallback to ico instead of throwing error by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10260 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10259 +* Check CAA record before issuing certificate by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10258 +* Revert "Check CAA record before issuing certificate" by @Meldiron in https://github.com/appwrite/appwrite/pull/10263 +* Test var id attribute by @fogelito in https://github.com/appwrite/appwrite/pull/10243 +* Add type attribute to the database creation flow by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10266 +* Add CAA validator by @Meldiron in https://github.com/appwrite/appwrite/pull/10267 +* Update database type to grids and legacy by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10273 +* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10272 +* Upgrade composer for utopia migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10274 +* Update SDK generator and sdks by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10271 +* Fix wrong resource path for audits by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10279 +* Update `grid` on resource events by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10282 +* Add readonly param to sequence, databaseId and collectionId by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10278 +* Update migrations by @abnegate in https://github.com/appwrite/appwrite/pull/10283 +* Add placeholder detection by @Meldiron in https://github.com/appwrite/appwrite/pull/10284 +* Update docker base to 0.10.3 by @abnegate in https://github.com/appwrite/appwrite/pull/10285 +* Make check for adding warning header stricter by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10293 +* Fix databases worker cache clearing bug by @abnegate in https://github.com/appwrite/appwrite/pull/10294 +* Reapply Redis functions queue by @Meldiron in https://github.com/appwrite/appwrite/pull/10299 +* Add new database query type tests by @abnegate in https://github.com/appwrite/appwrite/pull/10296 +* Update package by @abnegate in https://github.com/appwrite/appwrite/pull/10312 +* Update required attributes by @fogelito in https://github.com/appwrite/appwrite/pull/10311 +* Remove experiment warnings from bulk methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10310 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10313 +* Added internal file param to handle upload to internal bucket by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10321 +* Remove temp logging by @Meldiron in https://github.com/appwrite/appwrite/pull/10302 +* Improve sites test for stability by @Meldiron in https://github.com/appwrite/appwrite/pull/10331 +* Database lib bump to 0.71.15 by @fogelito in https://github.com/appwrite/appwrite/pull/10336 +* Clarify userId param in endpoints that create accounts by @ebenezerdon in https://github.com/appwrite/appwrite/pull/10117 +* Upgrade HTTP by @Meldiron in https://github.com/appwrite/appwrite/pull/10338 +* Remove unnessessary external dependnecies by @Meldiron in https://github.com/appwrite/appwrite/pull/10343 +* Sync main into 1.7.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10347 +* Fix TablesDB casing by @abnegate in https://github.com/appwrite/appwrite/pull/10346 +* Add cookies test by @Meldiron in https://github.com/appwrite/appwrite/pull/10352 +* Update token tests with jwt decode by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/10354 +* Utilize assets server for fonts by @Meldiron in https://github.com/appwrite/appwrite/pull/10358 +* Sync main into 1.7.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10359 +* Bump 1.7.x by @fogelito in https://github.com/appwrite/appwrite/pull/10365 +* Fix queue health by @loks0n in https://github.com/appwrite/appwrite/pull/10369 +* Allow publisher messaging override in scheduler by @loks0n in https://github.com/appwrite/appwrite/pull/10370 +* Add replacewith and deprecated since to account methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10377 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10376 +* Update CLI by @abnegate in https://github.com/appwrite/appwrite/pull/10390 +* Update default method in description by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10391 +* Rename namespace from tables-db to tablesdb in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10395 +* Update tables group in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10394 +* Update description for upsert methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10397 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10401 +* Added handling of database resources after migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10400 +* Revert "Added handling of database resources after migration" by @abnegate in https://github.com/appwrite/appwrite/pull/10406 +* Remove sdk deprecation warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10408 +* Mark Row response model's param with readonly by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10409 +* Update exception thrown when svg sanitization fails by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10416 +* Fix allow null params by @abnegate in https://github.com/appwrite/appwrite/pull/10417 +* Allow running tests with specific response format by @abnegate in https://github.com/appwrite/appwrite/pull/10418 +* Make webhooks publisher overridable by @loks0n in https://github.com/appwrite/appwrite/pull/10419 +* Check audits logs by @fogelito in https://github.com/appwrite/appwrite/pull/10414 +* Remove direct publisher calls by @loks0n in https://github.com/appwrite/appwrite/pull/10420 +* removed spaital type response and will be using the json type for the… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10433 +* Add tests for new time helpers by @abnegate in https://github.com/appwrite/appwrite/pull/10437 +* Move projects.list() to module by @Meldiron in https://github.com/appwrite/appwrite/pull/10441 +* Update cli to 9.1.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10442 +* Add requestBody param examples in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10431 +* Fix mysql tests by @abnegate in https://github.com/appwrite/appwrite/pull/10445 +* Upgrade platform lib to have older queue lib by @Meldiron in https://github.com/appwrite/appwrite/pull/10447 +* Fix router compression by @Meldiron in https://github.com/appwrite/appwrite/pull/10452 +* Upgrade http lib for backwards compatible default param by @Meldiron in https://github.com/appwrite/appwrite/pull/10455 +* Update examples by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10444 +* Automatic pr creation in sdk release script by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10457 +* Remove avatars command from cli by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10454 +* Remove deno from platforms array by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10453 +* Spatial type attributes sdk updates by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10463 +* Stats resources try catch by @fogelito in https://github.com/appwrite/appwrite/pull/10469 +* Move proxy endpoints to modules by @Meldiron in https://github.com/appwrite/appwrite/pull/10470 +* Add certificate valdiation override by @Meldiron in https://github.com/appwrite/appwrite/pull/10471 +* Generate SDKs by @abnegate in https://github.com/appwrite/appwrite/pull/10475 +* Spatial test tablesdb updates by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10473 +* Add colors to certificate logs by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10438 +* appwrite db bump by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10479 +* Bump database by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10480 +* Health db queues by @loks0n in https://github.com/appwrite/appwrite/pull/10482 +* Attempt small size for website dependency by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10485 +* Worker stop by @loks0n in https://github.com/appwrite/appwrite/pull/10498 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/10506 +* Stats resources and usage sorting by unique field by @fogelito in https://github.com/appwrite/appwrite/pull/10472 +* Add spatial column validation during required mode and tests for exis… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10509 +* Sub query variables order by by @fogelito in https://github.com/appwrite/appwrite/pull/10513 +* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10514 +* bump database 1.5.0 by @fogelito in https://github.com/appwrite/appwrite/pull/10515 +* Don't remove required attributes by @abnegate in https://github.com/appwrite/appwrite/pull/10516 +* Catch query exception on bulk update/delete by @abnegate in https://github.com/appwrite/appwrite/pull/10517 +* Update cli to 10.0.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10511 +* Add type_enum support and update docs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10496 +* Improve code readability for schedules by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10522 +* Include response model enum names by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10538 +* SDK releases by @abnegate in https://github.com/appwrite/appwrite/pull/10539 +* Fix health status enum by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10540 +* Update afterbuild fn by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10541 +* Update afterbuild to also pass adapter by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10545 +* Update `z-index` to be the highest by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9874 +* Update framework lib to 0.33.28 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10551 +* Fix enum typing for platform in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10553 +* Add enums for database type and column status by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10561 +* Fix activities by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10586 +* Fix logs truncation tests by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10585 +* Remove related data in realtime payload by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10590 +* Update composer dependencies by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10601 +* Update sdks add response models by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10554 +* Sanitize 5xx errors on realtime by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10598 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/10596 +* Add both collection and table id in the realtime by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10608 +* Chore bump db by @abnegate in https://github.com/appwrite/appwrite/pull/10611 +* Branded email for Console auth flows by @hmacr in https://github.com/appwrite/appwrite/pull/10501 +* Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10614 +* Add automatic releases by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10615 +* Feat txn sdks by @abnegate in https://github.com/appwrite/appwrite/pull/10621 +* Prevent empty releases in sdk release script by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10627 +* Update domains lib to 0.8.2 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10629 +* Fix txn API scope backwards compat by @abnegate in https://github.com/appwrite/appwrite/pull/10640 +* Fix block schedules by @loks0n in https://github.com/appwrite/appwrite/pull/10620 +* Update .NET SDK to 0.21.2 and improve release detection by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10641 +* Mmake methods protected for extending by @lohanidamodar in https://github.com/appwrite/appwrite/pull/10617 + +# Version 1.7.4 + +## What's Changed + +### Notable changes + +* Update console image to version 6.0.13 by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9891 + +### Fixes + +* Fix createDeployment chunk upload by @Meldiron in https://github.com/appwrite/appwrite/pull/9886 + +### Miscellaneous + +* Update version from 1.7.3 to 1.7.4 by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9893 + +# Version 1.7.3 + +## What's Changed + +### Notable changes + +* Allow unlimited deployment size by @Meldiron in https://github.com/appwrite/appwrite/pull/9866 +* Bump console to version 6.0.11 by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9881 + +### Fixes + +* Send deploymentResourceType in rules verification by @basert in https://github.com/appwrite/appwrite/pull/9859 +* Fix CNAME validation by @Meldiron in https://github.com/appwrite/appwrite/pull/9861 +* Fix bucket not included in path by @abnegate in https://github.com/appwrite/appwrite/pull/9864 +* Fix URL for view logs in github comment by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9875 +* Set owner and region while migrating rules by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9856 +* Remove _APP_DEFAULT_REGION because it is not a valid env var by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9883 + +### Miscellaneous + +* Only load error page for development mode by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9860 +* Make max deployment and build size configurable by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9863 +* Update flutter_web_auth_2 docs to match 4.x by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9858 +* Use unique filename for health check by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9842 +* Added encrypt property in the attribute string response model by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9868 +* Add sequence by @abnegate in https://github.com/appwrite/appwrite/pull/9865 +* Add builds worker group by @loks0n in https://github.com/appwrite/appwrite/pull/9873 +* updated errro for the string encryption by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9878 +* Revert "Add sequence" by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9879 +* Prepare 1.7.3 release by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9882 + # Version 1.6.2 ## What's Changed From 610a359160ee14ad610b5e1da2fbfc12a7bb14f6 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Mon, 20 Oct 2025 14:06:07 -0700 Subject: [PATCH 378/385] chore: clean up CHANGES.md 1. Remove PR authors because they don't render properly anyways 2. Format PR links to use markdown style links instead of plain URLs --- CHANGES.md | 1200 ++++++++++++++++++++++++++-------------------------- 1 file changed, 600 insertions(+), 600 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7d40c41aaf..74b46b7edc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,340 +4,340 @@ ### Notable changes -* Do not allow full range by @basert in https://github.com/appwrite/appwrite/pull/9847 -* Expose internal id as a part of auto increment id by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9713 -* Expose sequence by @abnegate in https://github.com/appwrite/appwrite/pull/9870 -* Add flutter 3.32 and dart 3.8 runtimes by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9914 -* Shorten commit url and branch url by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9919 -* Remove powered by from error pages by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9927 -* Enable resource limits on GIF previews by @basert in https://github.com/appwrite/appwrite/pull/9940 -* Only run maintenance task for projects accessed in last 24 hours by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9989 -* Add increment + decrement routes by @abnegate in https://github.com/appwrite/appwrite/pull/9986 -* Only run maintenance task for projects accessed in last 30 days by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9995 -* Update appwrite-assistant image version to 0.8.3 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10003 -* Update emails to use button by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9590 -* Create commit & branch url for first git deployment when site is linked to repo by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9969 -* Handle React Native schemes by @loks0n in https://github.com/appwrite/appwrite/pull/9650 -* Handle origin validation for web extensions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10107 -* Preview text for emails by @hmacr in https://github.com/appwrite/appwrite/pull/10198 -* Create email target when using email OTP registration by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10224 -* Add CSV imports by @abnegate in https://github.com/appwrite/appwrite/pull/10231 -* Add support for svg favicons by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10255 -* Realtime support for bulk api by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10096 -* Skip redundant subqueries in users list route by @abnegate in https://github.com/appwrite/appwrite/pull/10297 -* Add native sign in with Apple function template by @adityaoberai in https://github.com/appwrite/appwrite/pull/10286 -* Add support for HEAD requests by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10304 -* Update invite email copy by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10309 -* Increase dynamic API key expiration by @Meldiron in https://github.com/appwrite/appwrite/pull/10328 -* Add TablesDB service by @abnegate in https://github.com/appwrite/appwrite/pull/10333 -* Add execution.deploymentId to response model by @Meldiron in https://github.com/appwrite/appwrite/pull/10357 -* Switch Union China Pay to just Union Pay by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10372 and https://github.com/appwrite/appwrite/pull/10382 -* Add execution id and log id to response headers by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10379 -* Add executionId and client IP to function headers by @JoshiJoshiJoshi in https://github.com/appwrite/appwrite/pull/9147 -* Allow HEAD requests in function executions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10385 -* Add support for select queries when listing deployments by @Meldiron in https://github.com/appwrite/appwrite/pull/10380 -* Add spatial type attributes by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10356 and https://github.com/appwrite/appwrite/pull/10443 -* Add realtime support for bulk upserts by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10425 -* Add previewUrl to vcs comment from vcs controller by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10396 -* Rename verification SDK methods to be more specific by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10606 -* Add project name in email subject by @hmacr in https://github.com/appwrite/appwrite/pull/10609 -* Throw error when email is not available for account verification by @hmacr in https://github.com/appwrite/appwrite/pull/10533 -* Add support for transactions by @abnegate in https://github.com/appwrite/appwrite/pull/10023 and https://github.com/appwrite/appwrite/pull/10624 -* Use bcc only emails for smtp by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10644 +* Do not allow full range in [#9847](https://github.com/appwrite/appwrite/pull/9847) +* Expose internal id as a part of auto increment id in [#9713](https://github.com/appwrite/appwrite/pull/9713) +* Expose sequence in [#9870](https://github.com/appwrite/appwrite/pull/9870) +* Add flutter 3.32 and dart 3.8 runtimes in [#9914](https://github.com/appwrite/appwrite/pull/9914) +* Shorten commit url and branch url in [#9919](https://github.com/appwrite/appwrite/pull/9919) +* Remove powered by from error pages in [#9927](https://github.com/appwrite/appwrite/pull/9927) +* Enable resource limits on GIF previews in [#9940](https://github.com/appwrite/appwrite/pull/9940) +* Only run maintenance task for projects accessed in last 24 hours in [#9989](https://github.com/appwrite/appwrite/pull/9989) +* Add increment + decrement routes in [#9986](https://github.com/appwrite/appwrite/pull/9986) +* Only run maintenance task for projects accessed in last 30 days in [#9995](https://github.com/appwrite/appwrite/pull/9995) +* Update appwrite-assistant image version to 0.8.3 in [#10003](https://github.com/appwrite/appwrite/pull/10003) +* Update emails to use button in [#9590](https://github.com/appwrite/appwrite/pull/9590) +* Create commit & branch url for first git deployment when site is linked to repo in [#9969](https://github.com/appwrite/appwrite/pull/9969) +* Handle React Native schemes in [#9650](https://github.com/appwrite/appwrite/pull/9650) +* Handle origin validation for web extensions in [#10107](https://github.com/appwrite/appwrite/pull/10107) +* Preview text for emails in [#10198](https://github.com/appwrite/appwrite/pull/10198) +* Create email target when using email OTP registration in [#10224](https://github.com/appwrite/appwrite/pull/10224) +* Add CSV imports in [#10231](https://github.com/appwrite/appwrite/pull/10231) +* Add support for svg favicons in [#10255](https://github.com/appwrite/appwrite/pull/10255) +* Realtime support for bulk api in [#10096](https://github.com/appwrite/appwrite/pull/10096) +* Skip redundant subqueries in users list route in [#10297](https://github.com/appwrite/appwrite/pull/10297) +* Add native sign in with Apple function template in [#10286](https://github.com/appwrite/appwrite/pull/10286) +* Add support for HEAD requests in [#10304](https://github.com/appwrite/appwrite/pull/10304) +* Update invite email copy in [#10309](https://github.com/appwrite/appwrite/pull/10309) +* Increase dynamic API key expiration in [#10328](https://github.com/appwrite/appwrite/pull/10328) +* Add TablesDB service in [#10333](https://github.com/appwrite/appwrite/pull/10333) +* Add execution.deploymentId to response model in [#10357](https://github.com/appwrite/appwrite/pull/10357) +* Switch Union China Pay to just Union Pay in [#10372](https://github.com/appwrite/appwrite/pull/10372) and [#10382](https://github.com/appwrite/appwrite/pull/10382) +* Add execution id and log id to response headers in [#10379](https://github.com/appwrite/appwrite/pull/10379) +* Add executionId and client IP to function headers in [#9147](https://github.com/appwrite/appwrite/pull/9147) +* Allow HEAD requests in function executions in [#10385](https://github.com/appwrite/appwrite/pull/10385) +* Add support for select queries when listing deployments in [#10380](https://github.com/appwrite/appwrite/pull/10380) +* Add spatial type attributes in [#10356](https://github.com/appwrite/appwrite/pull/10356) and [#10443](https://github.com/appwrite/appwrite/pull/10443) +* Add realtime support for bulk upserts in [#10425](https://github.com/appwrite/appwrite/pull/10425) +* Add previewUrl to vcs comment from vcs controller in [#10396](https://github.com/appwrite/appwrite/pull/10396) +* Rename verification SDK methods to be more specific in [#10606](https://github.com/appwrite/appwrite/pull/10606) +* Add project name in email subject in [#10609](https://github.com/appwrite/appwrite/pull/10609) +* Throw error when email is not available for account verification in [#10533](https://github.com/appwrite/appwrite/pull/10533) +* Add support for transactions in [#10023](https://github.com/appwrite/appwrite/pull/10023) and [#10624](https://github.com/appwrite/appwrite/pull/10624) +* Use bcc only emails for smtp in [#10644](https://github.com/appwrite/appwrite/pull/10644) ### Fixes -* Fix rules on active deployment by @Meldiron in https://github.com/appwrite/appwrite/pull/9902 -* Fix for upserts with differing optional parameter sets by @abnegate in https://github.com/appwrite/appwrite/pull/9928 -* Fix teams deletion by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9888 -* Fix deletion logic by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9938 -* Update database for upsert fix by @abnegate in https://github.com/appwrite/appwrite/pull/9941 -* Fix expire format in account recovery, verification, phone and mfa by @jmastr in https://github.com/appwrite/appwrite/pull/9600 -* Fix github comments and deployment creation on branch deletion by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9949 -* Fix cache issues with proxy for deployment download by @Meldiron in https://github.com/appwrite/appwrite/pull/9971 -* Redirect rule parent resource by @Meldiron in https://github.com/appwrite/appwrite/pull/9982 -* Fix usage queues by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9946 -* Transfer control for the migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9997 -* Prevent 'Attribute "factors" must be an array' error by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10004 -* Fix all vcs urls missing region by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9998 -* Add readable error for csv imports by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9947 -* Fix missing screenshot logs by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10024 -* Update executor to fix s3 endpoint bug by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10036 -* Fix build duration calculation by @Meldiron in https://github.com/appwrite/appwrite/pull/10053 -* Fix logs order by @Meldiron in https://github.com/appwrite/appwrite/pull/10052 -* Fix platform check for Sites with automatic rule by @Meldiron in https://github.com/appwrite/appwrite/pull/10043 -* Increase cache ttl to ensure hits by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10079 -* Fix connect to existing repo flow by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10034 -* Fix migrations path and type by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10090 -* Fix JWT authentication database selection for admin mode by @arielweinberger in https://github.com/appwrite/appwrite/pull/10098 -* Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9999 -* Fix file tokens not working on file-security by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10120 -* Fix build activation race condition by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9952 -* Changed the default permission param of upsert document by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10129 -* Fix success validation in oauth2 redirect by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10130 -* Update OAuth2 redirect URLs by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10119 -* Fix specs with new env vars by @Meldiron in https://github.com/appwrite/appwrite/pull/10135 -* Skip deployment when commit is created by us by @hmacr in https://github.com/appwrite/appwrite/pull/10187 -* Use direct source for file-preview when empty by @hmacr in https://github.com/appwrite/appwrite/pull/10181 -* Better error message for invalid function scheduled time by @hmacr in https://github.com/appwrite/appwrite/pull/10201 -* Add defaultBranch in getRepository response by @hmacr in https://github.com/appwrite/appwrite/pull/10190 -* Filter sequence to int because any models skip rule checks by @abnegate in https://github.com/appwrite/appwrite/pull/10221 -* Fix 500 errors on robots and humans txt files by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10248 -* Fix atomic number ops with limit 0 by @abnegate in https://github.com/appwrite/appwrite/pull/10264 -* Update build command for flutter by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10288 -* Add a fallback locale by @Meldiron in https://github.com/appwrite/appwrite/pull/10307 -* Fix variables sharing across resources by @Meldiron in https://github.com/appwrite/appwrite/pull/10308 -* Fix uncaught invalid arg by @abnegate in https://github.com/appwrite/appwrite/pull/10318 -* Add missing upsert event by @abnegate in https://github.com/appwrite/appwrite/pull/10317 -* Improve font reliability by @Meldiron in https://github.com/appwrite/appwrite/pull/10332 -* Truncate logs in function worker by @samikshaaagarwal in https://github.com/appwrite/appwrite/pull/9773 -* Fix event template configuration issues by @adityaoberai in https://github.com/appwrite/appwrite/pull/10350 -* Fix users events & missed publisher logic for Functions by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10348 -* Fix incorrect file token expiry by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/10329 -* Fix upserting that makes no change by @fogelito in https://github.com/appwrite/appwrite/pull/10363 and https://github.com/appwrite/appwrite/pull/10364 -* Fix domain validator by @Meldiron in https://github.com/appwrite/appwrite/pull/10374 -* Apply sequence integer casting and attribute cleanup fixes to Row model, TablesDB tests, and document processing by @Copilot in https://github.com/appwrite/appwrite/pull/10383 -* Fix domain validator by @abnegate in https://github.com/appwrite/appwrite/pull/10386 -* Fix sequence removal by @abnegate in https://github.com/appwrite/appwrite/pull/10388 -* Fix TablesDB scopes by @abnegate in https://github.com/appwrite/appwrite/pull/10387 -* Fix request filter by @abnegate in https://github.com/appwrite/appwrite/pull/10389 -* Fix nested filter selects by @abnegate in https://github.com/appwrite/appwrite/pull/10393 -* Fix readonly attr stripping on write by @abnegate in https://github.com/appwrite/appwrite/pull/10405 -* Replace %s with mustache placeholder by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10392 -* Support array headers for set-cookie by @Meldiron in https://github.com/appwrite/appwrite/pull/10427 -* Fix put prefs structure validation by @fogelito in https://github.com/appwrite/appwrite/pull/10436 -* Fix oauth identity check by @Meldiron in https://github.com/appwrite/appwrite/pull/10460 -* Fix check by @abnegate in https://github.com/appwrite/appwrite/pull/10489 -* Fix database usage metrics by @Divyansha23 in https://github.com/appwrite/appwrite/pull/10483 -* Throw appropriate 400s from request filters by @abnegate in https://github.com/appwrite/appwrite/pull/10502 -* Catch query exception on bucket/file list by @abnegate in https://github.com/appwrite/appwrite/pull/10505 -* Use outputDirectory attribute from deployment by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/10571 -* Fix buildOutput attribute name in deployment check by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/10572 -* Update database for nested selection fix by @abnegate in https://github.com/appwrite/appwrite/pull/10577 -* Auto-allow sites domain for OAuth by @hmacr in https://github.com/appwrite/appwrite/pull/10503 -* Handle OIDC well-known endpoint errors by @hmacr in https://github.com/appwrite/appwrite/pull/10589 -* Correct invalid template links in Create temporary deployment endpoint by @Priyanshuthapliyal2005 in https://github.com/appwrite/appwrite/pull/10581 -* Update broken create table links in TablesDB docs by @Priyanshuthapliyal2005 in https://github.com/appwrite/appwrite/pull/10592 -* Fix cross API compatibility by @abnegate in https://github.com/appwrite/appwrite/pull/10626 -* Fix code 0 from databases on realtime by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10631 -* Throw duplicate error when function id already exists by @hmacr in https://github.com/appwrite/appwrite/pull/10618 +* Fix rules on active deployment in [#9902](https://github.com/appwrite/appwrite/pull/9902) +* Fix for upserts with differing optional parameter sets in [#9928](https://github.com/appwrite/appwrite/pull/9928) +* Fix teams deletion in [#9888](https://github.com/appwrite/appwrite/pull/9888) +* Fix deletion logic in [#9938](https://github.com/appwrite/appwrite/pull/9938) +* Update database for upsert fix in [#9941](https://github.com/appwrite/appwrite/pull/9941) +* Fix expire format in account recovery, verification, phone and mfa in [#9600](https://github.com/appwrite/appwrite/pull/9600) +* Fix github comments and deployment creation on branch deletion in [#9949](https://github.com/appwrite/appwrite/pull/9949) +* Fix cache issues with proxy for deployment download in [#9971](https://github.com/appwrite/appwrite/pull/9971) +* Redirect rule parent resource in [#9982](https://github.com/appwrite/appwrite/pull/9982) +* Fix usage queues in [#9946](https://github.com/appwrite/appwrite/pull/9946) +* Transfer control for the migration in [#9997](https://github.com/appwrite/appwrite/pull/9997) +* Prevent 'Attribute "factors" must be an array' error in [#10004](https://github.com/appwrite/appwrite/pull/10004) +* Fix all vcs urls missing region in [#9998](https://github.com/appwrite/appwrite/pull/9998) +* Add readable error for csv imports in [#9947](https://github.com/appwrite/appwrite/pull/9947) +* Fix missing screenshot logs in [#10024](https://github.com/appwrite/appwrite/pull/10024) +* Update executor to fix s3 endpoint bug in [#10036](https://github.com/appwrite/appwrite/pull/10036) +* Fix build duration calculation in [#10053](https://github.com/appwrite/appwrite/pull/10053) +* Fix logs order in [#10052](https://github.com/appwrite/appwrite/pull/10052) +* Fix platform check for Sites with automatic rule in [#10043](https://github.com/appwrite/appwrite/pull/10043) +* Increase cache ttl to ensure hits in [#10079](https://github.com/appwrite/appwrite/pull/10079) +* Fix connect to existing repo flow in [#10034](https://github.com/appwrite/appwrite/pull/10034) +* Fix migrations path and type in [#10090](https://github.com/appwrite/appwrite/pull/10090) +* Fix JWT authentication database selection for admin mode in [#10098](https://github.com/appwrite/appwrite/pull/10098) +* Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN in [#9999](https://github.com/appwrite/appwrite/pull/9999) +* Fix file tokens not working on file-security in [#10120](https://github.com/appwrite/appwrite/pull/10120) +* Fix build activation race condition in [#9952](https://github.com/appwrite/appwrite/pull/9952) +* Changed the default permission param of upsert document in [#10129](https://github.com/appwrite/appwrite/pull/10129) +* Fix success validation in oauth2 redirect in [#10130](https://github.com/appwrite/appwrite/pull/10130) +* Update OAuth2 redirect URLs in [#10119](https://github.com/appwrite/appwrite/pull/10119) +* Fix specs with new env vars in [#10135](https://github.com/appwrite/appwrite/pull/10135) +* Skip deployment when commit is created by us in [#10187](https://github.com/appwrite/appwrite/pull/10187) +* Use direct source for file-preview when empty in [#10181](https://github.com/appwrite/appwrite/pull/10181) +* Better error message for invalid function scheduled time in [#10201](https://github.com/appwrite/appwrite/pull/10201) +* Add defaultBranch in getRepository response in [#10190](https://github.com/appwrite/appwrite/pull/10190) +* Filter sequence to int because any models skip rule checks in [#10221](https://github.com/appwrite/appwrite/pull/10221) +* Fix 500 errors on robots and humans txt files in [#10248](https://github.com/appwrite/appwrite/pull/10248) +* Fix atomic number ops with limit 0 in [#10264](https://github.com/appwrite/appwrite/pull/10264) +* Update build command for flutter in [#10288](https://github.com/appwrite/appwrite/pull/10288) +* Add a fallback locale in [#10307](https://github.com/appwrite/appwrite/pull/10307) +* Fix variables sharing across resources in [#10308](https://github.com/appwrite/appwrite/pull/10308) +* Fix uncaught invalid arg in [#10318](https://github.com/appwrite/appwrite/pull/10318) +* Add missing upsert event in [#10317](https://github.com/appwrite/appwrite/pull/10317) +* Improve font reliability in [#10332](https://github.com/appwrite/appwrite/pull/10332) +* Truncate logs in function worker in [#9773](https://github.com/appwrite/appwrite/pull/9773) +* Fix event template configuration issues in [#10350](https://github.com/appwrite/appwrite/pull/10350) +* Fix users events & missed publisher logic for Functions in [#10348](https://github.com/appwrite/appwrite/pull/10348) +* Fix incorrect file token expiry in [#10329](https://github.com/appwrite/appwrite/pull/10329) +* Fix upserting that makes no change in [#10363](https://github.com/appwrite/appwrite/pull/10363) and [#10364](https://github.com/appwrite/appwrite/pull/10364) +* Fix domain validator in [#10374](https://github.com/appwrite/appwrite/pull/10374) +* Apply sequence integer casting and attribute cleanup fixes to Row model, TablesDB tests, and document processing in [#10383](https://github.com/appwrite/appwrite/pull/10383) +* Fix domain validator in [#10386](https://github.com/appwrite/appwrite/pull/10386) +* Fix sequence removal in [#10388](https://github.com/appwrite/appwrite/pull/10388) +* Fix TablesDB scopes in [#10387](https://github.com/appwrite/appwrite/pull/10387) +* Fix request filter in [#10389](https://github.com/appwrite/appwrite/pull/10389) +* Fix nested filter selects in [#10393](https://github.com/appwrite/appwrite/pull/10393) +* Fix readonly attr stripping on write in [#10405](https://github.com/appwrite/appwrite/pull/10405) +* Replace %s with mustache placeholder in [#10392](https://github.com/appwrite/appwrite/pull/10392) +* Support array headers for set-cookie in [#10427](https://github.com/appwrite/appwrite/pull/10427) +* Fix put prefs structure validation in [#10436](https://github.com/appwrite/appwrite/pull/10436) +* Fix oauth identity check in [#10460](https://github.com/appwrite/appwrite/pull/10460) +* Fix check in [#10489](https://github.com/appwrite/appwrite/pull/10489) +* Fix database usage metrics in [#10483](https://github.com/appwrite/appwrite/pull/10483) +* Throw appropriate 400s from request filters in [#10502](https://github.com/appwrite/appwrite/pull/10502) +* Catch query exception on bucket/file list in [#10505](https://github.com/appwrite/appwrite/pull/10505) +* Use outputDirectory attribute from deployment in [#10571](https://github.com/appwrite/appwrite/pull/10571) +* Fix buildOutput attribute name in deployment check in [#10572](https://github.com/appwrite/appwrite/pull/10572) +* Update database for nested selection fix in [#10577](https://github.com/appwrite/appwrite/pull/10577) +* Auto-allow sites domain for OAuth in [#10503](https://github.com/appwrite/appwrite/pull/10503) +* Handle OIDC well-known endpoint errors in [#10589](https://github.com/appwrite/appwrite/pull/10589) +* Correct invalid template links in Create temporary deployment endpoint in [#10581](https://github.com/appwrite/appwrite/pull/10581) +* Update broken create table links in TablesDB docs in [#10592](https://github.com/appwrite/appwrite/pull/10592) +* Fix cross API compatibility in [#10626](https://github.com/appwrite/appwrite/pull/10626) +* Fix code 0 from databases on realtime in [#10631](https://github.com/appwrite/appwrite/pull/10631) +* Throw duplicate error when function id already exists in [#10618](https://github.com/appwrite/appwrite/pull/10618) ### Miscellaneous -* Fix task coroutine hooks by @basert in https://github.com/appwrite/appwrite/pull/9850 -* Feat sync encrypt updates by @abnegate in https://github.com/appwrite/appwrite/pull/9871 -* Revert "Feat sync encrypt updates" by @abnegate in https://github.com/appwrite/appwrite/pull/9877 -* Add builds worker group by @loks0n in https://github.com/appwrite/appwrite/pull/9872 -* Revert encrypted attribute changes by @abnegate in https://github.com/appwrite/appwrite/pull/9898 -* Update sdk generator and sdks by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9849 -* Release cli by @abnegate in https://github.com/appwrite/appwrite/pull/9900 -* Improve how rules are fetched by @Meldiron in https://github.com/appwrite/appwrite/pull/9915 -* Sync 1.6 by @abnegate in https://github.com/appwrite/appwrite/pull/9920 -* Update messaging library by @lohanidamodar in https://github.com/appwrite/appwrite/pull/9764 -* Disable TCP hook on stats resources by @abnegate in https://github.com/appwrite/appwrite/pull/9932 -* Remove JSON index on roles due to MySQL bug by @fogelito in https://github.com/appwrite/appwrite/pull/9924 -* Update queue by @abnegate in https://github.com/appwrite/appwrite/pull/9936 -* Fix flaky account tests by @loks0n in https://github.com/appwrite/appwrite/pull/9954 -* Fix flaky messaging test by @loks0n in https://github.com/appwrite/appwrite/pull/9957 -* Make usage tests robust by @loks0n in https://github.com/appwrite/appwrite/pull/9956 -* Increase deployment timeouts in tests by @loks0n in https://github.com/appwrite/appwrite/pull/9955 -* Graceful shutdown on SIGTERM by @basert in https://github.com/appwrite/appwrite/pull/9890 -* Bring back telemetry for storage by @basert in https://github.com/appwrite/appwrite/pull/9903 -* Update version to 1.7.4 and add experimental warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9959 -* Return queue pre-fetch results by @basert in https://github.com/appwrite/appwrite/pull/9731 -* Update SDK versions by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9987 -* Restore unique filename for health check #9842 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9993 -* Add after build hook by @loks0n in https://github.com/appwrite/appwrite/pull/9996 -* Remove endpoint selector by @loks0n in https://github.com/appwrite/appwrite/pull/10000 -* Use static code instead of astro in tests by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9966 -* Add ref param to vcs list contents by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9991 -* Update coderabbit config file by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10005 -* TAR support by @loks0n in https://github.com/appwrite/appwrite/pull/10016 -* Update delete project scope by @shimonewman in https://github.com/appwrite/appwrite/pull/10017 -* Lazy-load relationships by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9669 -* Revert "Feat: Lazy-load relationships" by @abnegate in https://github.com/appwrite/appwrite/pull/10018 -* Revert "Update delete project scope" by @abnegate in https://github.com/appwrite/appwrite/pull/10022 -* 1.8.x by @abnegate in https://github.com/appwrite/appwrite/pull/9985 -* Update cli version and add bulk operation warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10007 -* Update Appwrite description to include Sites, add MCP to products list by @ebenezerdon in https://github.com/appwrite/appwrite/pull/9867 -* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10026 -* Fix duplication of platforms in swagger specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10008 -* Update react native sdk and changelog by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10025 -* Update delete project signature by @shimonewman in https://github.com/appwrite/appwrite/pull/10028 -* Fix Golang SDK examples for docs by @adityaoberai in https://github.com/appwrite/appwrite/pull/10001 -* Revert "worker: Graceful shutdown on SIGTERM" by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10035 -* Fix benchmark CI by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10055 -* Use ->action(...)) instead of ->callback([$this, 'action']); by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9967 -* Override project via custom domains log by @shimonewman in https://github.com/appwrite/appwrite/pull/10011 -* Add database worker job logging by @abnegate in https://github.com/appwrite/appwrite/pull/10056 -* Add runtimeEntrypoint param by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10062 -* Add missing injections by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10061 -* Replace Console loop with Swoole Timer for stats resource m… by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10054 -* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10063 -* Fix parameter order in action function for robots.txt route by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10067 -* Preview endpoint logging by @Meldiron in https://github.com/appwrite/appwrite/pull/10068 -* Fix flakyness of account tests by @Meldiron in https://github.com/appwrite/appwrite/pull/10066 -* Update cli to 8.1.0 and add changelog by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10070 -* Update composer.json and composer.lock to include appwrite-lab… by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10051 -* Fix tests, for `Cloud` by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10085 -* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10084 -* Revert "chore: update composer.json and composer.lock to include appwrite-lab…" by @abnegate in https://github.com/appwrite/appwrite/pull/10086 -* Update README to add Bulk API link by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10095 -* Add redis publisher to schedule base if available by @abnegate in https://github.com/appwrite/appwrite/pull/10099 -* Fix site template test by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10104 -* Update nodejs 17.1.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10088 -* Update README.md to add Upsert announcement by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10112 -* Reduce delete batch size by @fogelito in https://github.com/appwrite/appwrite/pull/10128 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10134 -* Speed up tests by @Meldiron in https://github.com/appwrite/appwrite/pull/10127 -* Update cli to 8.2.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10136 -* Prevent injected $user from being shadowed by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10150 -* Update react native to 0.10.1 and dotnet to 0.14.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10138 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10153 -* Update cli 8.2.1 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10155 -* Fix build usage specification by @loks0n in https://github.com/appwrite/appwrite/pull/10157 -* Handle redirect validator in specs + GraphQL type mapper by @abnegate in https://github.com/appwrite/appwrite/pull/10158 -* Update dart 16.1.0, flutter 17.0.2 and cli 8.2.2 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10161 -* Improve invalid scheme error in origin check by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10164 -* 1.7.x by @Meldiron in https://github.com/appwrite/appwrite/pull/9897 -* Added the cases of null permissions in the upsert route and update th… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10179 -* Fix 1.7.x specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10197 -* Suppress git-action exception in deployment worker by @hmacr in https://github.com/appwrite/appwrite/pull/10199 -* Stats-usage on redis by @loks0n in https://github.com/appwrite/appwrite/pull/10156 -* Fix templates on `1.7.x`. by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10203 -* Change preview & body for MFA email by @hmacr in https://github.com/appwrite/appwrite/pull/10205 -* Add docs for nestedType, encode, from and toMap by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10204 -* Update sdks 1.7.x by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10202 -* Update migration release by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10222 -* Remove sequence on incoming docs by @abnegate in https://github.com/appwrite/appwrite/pull/10228 -* Filter certificates renewal task in maintenance by region by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10227 -* Move changelog to sdks platforms array by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10233 -* Update changelog and sdk gen by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10247 -* Telemetry for cache hits and misses by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10240 -* Add model examples + additonal examples to specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10249 -* Update favicons endpoint to fallback to ico instead of throwing error by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10260 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10259 -* Check CAA record before issuing certificate by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10258 -* Revert "Check CAA record before issuing certificate" by @Meldiron in https://github.com/appwrite/appwrite/pull/10263 -* Test var id attribute by @fogelito in https://github.com/appwrite/appwrite/pull/10243 -* Add type attribute to the database creation flow by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10266 -* Add CAA validator by @Meldiron in https://github.com/appwrite/appwrite/pull/10267 -* Update database type to grids and legacy by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10273 -* Update README.md by @LauraDuRy in https://github.com/appwrite/appwrite/pull/10272 -* Upgrade composer for utopia migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10274 -* Update SDK generator and sdks by @christyjacob4 in https://github.com/appwrite/appwrite/pull/10271 -* Fix wrong resource path for audits by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10279 -* Update `grid` on resource events by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10282 -* Add readonly param to sequence, databaseId and collectionId by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10278 -* Update migrations by @abnegate in https://github.com/appwrite/appwrite/pull/10283 -* Add placeholder detection by @Meldiron in https://github.com/appwrite/appwrite/pull/10284 -* Update docker base to 0.10.3 by @abnegate in https://github.com/appwrite/appwrite/pull/10285 -* Make check for adding warning header stricter by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10293 -* Fix databases worker cache clearing bug by @abnegate in https://github.com/appwrite/appwrite/pull/10294 -* Reapply Redis functions queue by @Meldiron in https://github.com/appwrite/appwrite/pull/10299 -* Add new database query type tests by @abnegate in https://github.com/appwrite/appwrite/pull/10296 -* Update package by @abnegate in https://github.com/appwrite/appwrite/pull/10312 -* Update required attributes by @fogelito in https://github.com/appwrite/appwrite/pull/10311 -* Remove experiment warnings from bulk methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10310 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10313 -* Added internal file param to handle upload to internal bucket by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10321 -* Remove temp logging by @Meldiron in https://github.com/appwrite/appwrite/pull/10302 -* Improve sites test for stability by @Meldiron in https://github.com/appwrite/appwrite/pull/10331 -* Database lib bump to 0.71.15 by @fogelito in https://github.com/appwrite/appwrite/pull/10336 -* Clarify userId param in endpoints that create accounts by @ebenezerdon in https://github.com/appwrite/appwrite/pull/10117 -* Upgrade HTTP by @Meldiron in https://github.com/appwrite/appwrite/pull/10338 -* Remove unnessessary external dependnecies by @Meldiron in https://github.com/appwrite/appwrite/pull/10343 -* Sync main into 1.7.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10347 -* Fix TablesDB casing by @abnegate in https://github.com/appwrite/appwrite/pull/10346 -* Add cookies test by @Meldiron in https://github.com/appwrite/appwrite/pull/10352 -* Update token tests with jwt decode by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/10354 -* Utilize assets server for fonts by @Meldiron in https://github.com/appwrite/appwrite/pull/10358 -* Sync main into 1.7.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/10359 -* Bump 1.7.x by @fogelito in https://github.com/appwrite/appwrite/pull/10365 -* Fix queue health by @loks0n in https://github.com/appwrite/appwrite/pull/10369 -* Allow publisher messaging override in scheduler by @loks0n in https://github.com/appwrite/appwrite/pull/10370 -* Add replacewith and deprecated since to account methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10377 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10376 -* Update CLI by @abnegate in https://github.com/appwrite/appwrite/pull/10390 -* Update default method in description by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10391 -* Rename namespace from tables-db to tablesdb in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10395 -* Update tables group in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10394 -* Update description for upsert methods by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10397 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10401 -* Added handling of database resources after migration by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10400 -* Revert "Added handling of database resources after migration" by @abnegate in https://github.com/appwrite/appwrite/pull/10406 -* Remove sdk deprecation warnings by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10408 -* Mark Row response model's param with readonly by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10409 -* Update exception thrown when svg sanitization fails by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10416 -* Fix allow null params by @abnegate in https://github.com/appwrite/appwrite/pull/10417 -* Allow running tests with specific response format by @abnegate in https://github.com/appwrite/appwrite/pull/10418 -* Make webhooks publisher overridable by @loks0n in https://github.com/appwrite/appwrite/pull/10419 -* Check audits logs by @fogelito in https://github.com/appwrite/appwrite/pull/10414 -* Remove direct publisher calls by @loks0n in https://github.com/appwrite/appwrite/pull/10420 -* removed spaital type response and will be using the json type for the… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10433 -* Add tests for new time helpers by @abnegate in https://github.com/appwrite/appwrite/pull/10437 -* Move projects.list() to module by @Meldiron in https://github.com/appwrite/appwrite/pull/10441 -* Update cli to 9.1.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10442 -* Add requestBody param examples in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10431 -* Fix mysql tests by @abnegate in https://github.com/appwrite/appwrite/pull/10445 -* Upgrade platform lib to have older queue lib by @Meldiron in https://github.com/appwrite/appwrite/pull/10447 -* Fix router compression by @Meldiron in https://github.com/appwrite/appwrite/pull/10452 -* Upgrade http lib for backwards compatible default param by @Meldiron in https://github.com/appwrite/appwrite/pull/10455 -* Update examples by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10444 -* Automatic pr creation in sdk release script by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10457 -* Remove avatars command from cli by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10454 -* Remove deno from platforms array by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10453 -* Spatial type attributes sdk updates by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10463 -* Stats resources try catch by @fogelito in https://github.com/appwrite/appwrite/pull/10469 -* Move proxy endpoints to modules by @Meldiron in https://github.com/appwrite/appwrite/pull/10470 -* Add certificate valdiation override by @Meldiron in https://github.com/appwrite/appwrite/pull/10471 -* Generate SDKs by @abnegate in https://github.com/appwrite/appwrite/pull/10475 -* Spatial test tablesdb updates by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10473 -* Add colors to certificate logs by @vermakhushboo in https://github.com/appwrite/appwrite/pull/10438 -* appwrite db bump by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10479 -* Bump database by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10480 -* Health db queues by @loks0n in https://github.com/appwrite/appwrite/pull/10482 -* Attempt small size for website dependency by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10485 -* Worker stop by @loks0n in https://github.com/appwrite/appwrite/pull/10498 -* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/10506 -* Stats resources and usage sorting by unique field by @fogelito in https://github.com/appwrite/appwrite/pull/10472 -* Add spatial column validation during required mode and tests for exis… by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10509 -* Sub query variables order by by @fogelito in https://github.com/appwrite/appwrite/pull/10513 -* Update README.md by @Veera-mulge in https://github.com/appwrite/appwrite/pull/10514 -* bump database 1.5.0 by @fogelito in https://github.com/appwrite/appwrite/pull/10515 -* Don't remove required attributes by @abnegate in https://github.com/appwrite/appwrite/pull/10516 -* Catch query exception on bulk update/delete by @abnegate in https://github.com/appwrite/appwrite/pull/10517 -* Update cli to 10.0.0 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10511 -* Add type_enum support and update docs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10496 -* Improve code readability for schedules by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10522 -* Include response model enum names by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10538 -* SDK releases by @abnegate in https://github.com/appwrite/appwrite/pull/10539 -* Fix health status enum by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10540 -* Update afterbuild fn by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10541 -* Update afterbuild to also pass adapter by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10545 -* Update `z-index` to be the highest by @ItzNotABug in https://github.com/appwrite/appwrite/pull/9874 -* Update framework lib to 0.33.28 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10551 -* Fix enum typing for platform in specs by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10553 -* Add enums for database type and column status by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10561 -* Fix activities by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10586 -* Fix logs truncation tests by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10585 -* Remove related data in realtime payload by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10590 -* Update composer dependencies by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10601 -* Update sdks add response models by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10554 -* Sanitize 5xx errors on realtime by @ItzNotABug in https://github.com/appwrite/appwrite/pull/10598 -* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/10596 -* Add both collection and table id in the realtime by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/10608 -* Chore bump db by @abnegate in https://github.com/appwrite/appwrite/pull/10611 -* Branded email for Console auth flows by @hmacr in https://github.com/appwrite/appwrite/pull/10501 -* Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10614 -* Add automatic releases by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10615 -* Feat txn sdks by @abnegate in https://github.com/appwrite/appwrite/pull/10621 -* Prevent empty releases in sdk release script by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10627 -* Update domains lib to 0.8.2 by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10629 -* Fix txn API scope backwards compat by @abnegate in https://github.com/appwrite/appwrite/pull/10640 -* Fix block schedules by @loks0n in https://github.com/appwrite/appwrite/pull/10620 -* Update .NET SDK to 0.21.2 and improve release detection by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/10641 -* Mmake methods protected for extending by @lohanidamodar in https://github.com/appwrite/appwrite/pull/10617 +* Fix task coroutine hooks in [#9850](https://github.com/appwrite/appwrite/pull/9850) +* Feat sync encrypt updates in [#9871](https://github.com/appwrite/appwrite/pull/9871) +* Revert "Feat sync encrypt updates" in [#9877](https://github.com/appwrite/appwrite/pull/9877) +* Add builds worker group in [#9872](https://github.com/appwrite/appwrite/pull/9872) +* Revert encrypted attribute changes in [#9898](https://github.com/appwrite/appwrite/pull/9898) +* Update sdk generator and sdks in [#9849](https://github.com/appwrite/appwrite/pull/9849) +* Release cli in [#9900](https://github.com/appwrite/appwrite/pull/9900) +* Improve how rules are fetched in [#9915](https://github.com/appwrite/appwrite/pull/9915) +* Sync 1.6 in [#9920](https://github.com/appwrite/appwrite/pull/9920) +* Update messaging library in [#9764](https://github.com/appwrite/appwrite/pull/9764) +* Disable TCP hook on stats resources in [#9932](https://github.com/appwrite/appwrite/pull/9932) +* Remove JSON index on roles due to MySQL bug in [#9924](https://github.com/appwrite/appwrite/pull/9924) +* Update queue in [#9936](https://github.com/appwrite/appwrite/pull/9936) +* Fix flaky account tests in [#9954](https://github.com/appwrite/appwrite/pull/9954) +* Fix flaky messaging test in [#9957](https://github.com/appwrite/appwrite/pull/9957) +* Make usage tests robust in [#9956](https://github.com/appwrite/appwrite/pull/9956) +* Increase deployment timeouts in tests in [#9955](https://github.com/appwrite/appwrite/pull/9955) +* Graceful shutdown on SIGTERM in [#9890](https://github.com/appwrite/appwrite/pull/9890) +* Bring back telemetry for storage in [#9903](https://github.com/appwrite/appwrite/pull/9903) +* Update version to 1.7.4 and add experimental warnings in [#9959](https://github.com/appwrite/appwrite/pull/9959) +* Return queue pre-fetch results in [#9731](https://github.com/appwrite/appwrite/pull/9731) +* Update SDK versions in [#9987](https://github.com/appwrite/appwrite/pull/9987) +* Restore unique filename for health check #9842 in [#9993](https://github.com/appwrite/appwrite/pull/9993) +* Add after build hook in [#9996](https://github.com/appwrite/appwrite/pull/9996) +* Remove endpoint selector in [#10000](https://github.com/appwrite/appwrite/pull/10000) +* Use static code instead of astro in tests in [#9966](https://github.com/appwrite/appwrite/pull/9966) +* Add ref param to vcs list contents in [#9991](https://github.com/appwrite/appwrite/pull/9991) +* Update coderabbit config file in [#10005](https://github.com/appwrite/appwrite/pull/10005) +* TAR support in [#10016](https://github.com/appwrite/appwrite/pull/10016) +* Update delete project scope in [#10017](https://github.com/appwrite/appwrite/pull/10017) +* Lazy-load relationships in [#9669](https://github.com/appwrite/appwrite/pull/9669) +* Revert "Feat: Lazy-load relationships" in [#10018](https://github.com/appwrite/appwrite/pull/10018) +* Revert "Update delete project scope" in [#10022](https://github.com/appwrite/appwrite/pull/10022) +* 1.8.x in [#9985](https://github.com/appwrite/appwrite/pull/9985) +* Update cli version and add bulk operation warnings in [#10007](https://github.com/appwrite/appwrite/pull/10007) +* Update Appwrite description to include Sites, add MCP to products list in [#9867](https://github.com/appwrite/appwrite/pull/9867) +* Update README.md in [#10026](https://github.com/appwrite/appwrite/pull/10026) +* Fix duplication of platforms in swagger specs in [#10008](https://github.com/appwrite/appwrite/pull/10008) +* Update react native sdk and changelog in [#10025](https://github.com/appwrite/appwrite/pull/10025) +* Update delete project signature in [#10028](https://github.com/appwrite/appwrite/pull/10028) +* Fix Golang SDK examples for docs in [#10001](https://github.com/appwrite/appwrite/pull/10001) +* Revert "worker: Graceful shutdown on SIGTERM" in [#10035](https://github.com/appwrite/appwrite/pull/10035) +* Fix benchmark CI in [#10055](https://github.com/appwrite/appwrite/pull/10055) +* Use ->action(...)) instead of ->callback([$this, 'action']); in [#9967](https://github.com/appwrite/appwrite/pull/9967) +* Override project via custom domains log in [#10011](https://github.com/appwrite/appwrite/pull/10011) +* Add database worker job logging in [#10056](https://github.com/appwrite/appwrite/pull/10056) +* Add runtimeEntrypoint param in [#10062](https://github.com/appwrite/appwrite/pull/10062) +* Add missing injections in [#10061](https://github.com/appwrite/appwrite/pull/10061) +* Replace Console loop with Swoole Timer for stats resource m… in [#10054](https://github.com/appwrite/appwrite/pull/10054) +* Update README.md in [#10063](https://github.com/appwrite/appwrite/pull/10063) +* Fix parameter order in action function for robots.txt route in [#10067](https://github.com/appwrite/appwrite/pull/10067) +* Preview endpoint logging in [#10068](https://github.com/appwrite/appwrite/pull/10068) +* Fix flakyness of account tests in [#10066](https://github.com/appwrite/appwrite/pull/10066) +* Update cli to 8.1.0 and add changelog in [#10070](https://github.com/appwrite/appwrite/pull/10070) +* Update composer.json and composer.lock to include appwrite-lab… in [#10051](https://github.com/appwrite/appwrite/pull/10051) +* Fix tests, for `Cloud` in [#10085](https://github.com/appwrite/appwrite/pull/10085) +* Update README.md in [#10084](https://github.com/appwrite/appwrite/pull/10084) +* Revert "chore: update composer.json and composer.lock to include appwrite-lab…" in [#10086](https://github.com/appwrite/appwrite/pull/10086) +* Update README to add Bulk API link in [#10095](https://github.com/appwrite/appwrite/pull/10095) +* Add redis publisher to schedule base if available in [#10099](https://github.com/appwrite/appwrite/pull/10099) +* Fix site template test in [#10104](https://github.com/appwrite/appwrite/pull/10104) +* Update nodejs 17.1.0 in [#10088](https://github.com/appwrite/appwrite/pull/10088) +* Update README.md to add Upsert announcement in [#10112](https://github.com/appwrite/appwrite/pull/10112) +* Reduce delete batch size in [#10128](https://github.com/appwrite/appwrite/pull/10128) +* Update README.md in [#10134](https://github.com/appwrite/appwrite/pull/10134) +* Speed up tests in [#10127](https://github.com/appwrite/appwrite/pull/10127) +* Update cli to 8.2.0 in [#10136](https://github.com/appwrite/appwrite/pull/10136) +* Prevent injected $user from being shadowed in [#10150](https://github.com/appwrite/appwrite/pull/10150) +* Update react native to 0.10.1 and dotnet to 0.14.0 in [#10138](https://github.com/appwrite/appwrite/pull/10138) +* Update README.md in [#10153](https://github.com/appwrite/appwrite/pull/10153) +* Update cli 8.2.1 in [#10155](https://github.com/appwrite/appwrite/pull/10155) +* Fix build usage specification in [#10157](https://github.com/appwrite/appwrite/pull/10157) +* Handle redirect validator in specs + GraphQL type mapper in [#10158](https://github.com/appwrite/appwrite/pull/10158) +* Update dart 16.1.0, flutter 17.0.2 and cli 8.2.2 in [#10161](https://github.com/appwrite/appwrite/pull/10161) +* Improve invalid scheme error in origin check in [#10164](https://github.com/appwrite/appwrite/pull/10164) +* 1.7.x in [#9897](https://github.com/appwrite/appwrite/pull/9897) +* Added the cases of null permissions in the upsert route and update th… in [#10179](https://github.com/appwrite/appwrite/pull/10179) +* Fix 1.7.x specs in [#10197](https://github.com/appwrite/appwrite/pull/10197) +* Suppress git-action exception in deployment worker in [#10199](https://github.com/appwrite/appwrite/pull/10199) +* Stats-usage on redis in [#10156](https://github.com/appwrite/appwrite/pull/10156) +* Fix templates on `1.7.x`. in [#10203](https://github.com/appwrite/appwrite/pull/10203) +* Change preview & body for MFA email in [#10205](https://github.com/appwrite/appwrite/pull/10205) +* Add docs for nestedType, encode, from and toMap in [#10204](https://github.com/appwrite/appwrite/pull/10204) +* Update sdks 1.7.x in [#10202](https://github.com/appwrite/appwrite/pull/10202) +* Update migration release in [#10222](https://github.com/appwrite/appwrite/pull/10222) +* Remove sequence on incoming docs in [#10228](https://github.com/appwrite/appwrite/pull/10228) +* Filter certificates renewal task in maintenance by region in [#10227](https://github.com/appwrite/appwrite/pull/10227) +* Move changelog to sdks platforms array in [#10233](https://github.com/appwrite/appwrite/pull/10233) +* Update changelog and sdk gen in [#10247](https://github.com/appwrite/appwrite/pull/10247) +* Telemetry for cache hits and misses in [#10240](https://github.com/appwrite/appwrite/pull/10240) +* Add model examples + additonal examples to specs in [#10249](https://github.com/appwrite/appwrite/pull/10249) +* Update favicons endpoint to fallback to ico instead of throwing error in [#10260](https://github.com/appwrite/appwrite/pull/10260) +* Update README.md in [#10259](https://github.com/appwrite/appwrite/pull/10259) +* Check CAA record before issuing certificate in [#10258](https://github.com/appwrite/appwrite/pull/10258) +* Revert "Check CAA record before issuing certificate" in [#10263](https://github.com/appwrite/appwrite/pull/10263) +* Test var id attribute in [#10243](https://github.com/appwrite/appwrite/pull/10243) +* Add type attribute to the database creation flow in [#10266](https://github.com/appwrite/appwrite/pull/10266) +* Add CAA validator in [#10267](https://github.com/appwrite/appwrite/pull/10267) +* Update database type to grids and legacy in [#10273](https://github.com/appwrite/appwrite/pull/10273) +* Update README.md in [#10272](https://github.com/appwrite/appwrite/pull/10272) +* Upgrade composer for utopia migration in [#10274](https://github.com/appwrite/appwrite/pull/10274) +* Update SDK generator and sdks in [#10271](https://github.com/appwrite/appwrite/pull/10271) +* Fix wrong resource path for audits in [#10279](https://github.com/appwrite/appwrite/pull/10279) +* Update `grid` on resource events in [#10282](https://github.com/appwrite/appwrite/pull/10282) +* Add readonly param to sequence, databaseId and collectionId in [#10278](https://github.com/appwrite/appwrite/pull/10278) +* Update migrations in [#10283](https://github.com/appwrite/appwrite/pull/10283) +* Add placeholder detection in [#10284](https://github.com/appwrite/appwrite/pull/10284) +* Update docker base to 0.10.3 in [#10285](https://github.com/appwrite/appwrite/pull/10285) +* Make check for adding warning header stricter in [#10293](https://github.com/appwrite/appwrite/pull/10293) +* Fix databases worker cache clearing bug in [#10294](https://github.com/appwrite/appwrite/pull/10294) +* Reapply Redis functions queue in [#10299](https://github.com/appwrite/appwrite/pull/10299) +* Add new database query type tests in [#10296](https://github.com/appwrite/appwrite/pull/10296) +* Update package in [#10312](https://github.com/appwrite/appwrite/pull/10312) +* Update required attributes in [#10311](https://github.com/appwrite/appwrite/pull/10311) +* Remove experiment warnings from bulk methods in [#10310](https://github.com/appwrite/appwrite/pull/10310) +* Update README.md in [#10313](https://github.com/appwrite/appwrite/pull/10313) +* Added internal file param to handle upload to internal bucket in [#10321](https://github.com/appwrite/appwrite/pull/10321) +* Remove temp logging in [#10302](https://github.com/appwrite/appwrite/pull/10302) +* Improve sites test for stability in [#10331](https://github.com/appwrite/appwrite/pull/10331) +* Database lib bump to 0.71.15 in [#10336](https://github.com/appwrite/appwrite/pull/10336) +* Clarify userId param in endpoints that create accounts in [#10117](https://github.com/appwrite/appwrite/pull/10117) +* Upgrade HTTP in [#10338](https://github.com/appwrite/appwrite/pull/10338) +* Remove unnessessary external dependnecies in [#10343](https://github.com/appwrite/appwrite/pull/10343) +* Sync main into 1.7.x in [#10347](https://github.com/appwrite/appwrite/pull/10347) +* Fix TablesDB casing in [#10346](https://github.com/appwrite/appwrite/pull/10346) +* Add cookies test in [#10352](https://github.com/appwrite/appwrite/pull/10352) +* Update token tests with jwt decode in [#10354](https://github.com/appwrite/appwrite/pull/10354) +* Utilize assets server for fonts in [#10358](https://github.com/appwrite/appwrite/pull/10358) +* Sync main into 1.7.x in [#10359](https://github.com/appwrite/appwrite/pull/10359) +* Bump 1.7.x in [#10365](https://github.com/appwrite/appwrite/pull/10365) +* Fix queue health in [#10369](https://github.com/appwrite/appwrite/pull/10369) +* Allow publisher messaging override in scheduler in [#10370](https://github.com/appwrite/appwrite/pull/10370) +* Add replacewith and deprecated since to account methods in [#10377](https://github.com/appwrite/appwrite/pull/10377) +* Update README.md in [#10376](https://github.com/appwrite/appwrite/pull/10376) +* Update CLI in [#10390](https://github.com/appwrite/appwrite/pull/10390) +* Update default method in description in [#10391](https://github.com/appwrite/appwrite/pull/10391) +* Rename namespace from tables-db to tablesdb in specs in [#10395](https://github.com/appwrite/appwrite/pull/10395) +* Update tables group in specs in [#10394](https://github.com/appwrite/appwrite/pull/10394) +* Update description for upsert methods in [#10397](https://github.com/appwrite/appwrite/pull/10397) +* Update README.md in [#10401](https://github.com/appwrite/appwrite/pull/10401) +* Added handling of database resources after migration in [#10400](https://github.com/appwrite/appwrite/pull/10400) +* Revert "Added handling of database resources after migration" in [#10406](https://github.com/appwrite/appwrite/pull/10406) +* Remove sdk deprecation warnings in [#10408](https://github.com/appwrite/appwrite/pull/10408) +* Mark Row response model's param with readonly in [#10409](https://github.com/appwrite/appwrite/pull/10409) +* Update exception thrown when svg sanitization fails in [#10416](https://github.com/appwrite/appwrite/pull/10416) +* Fix allow null params in [#10417](https://github.com/appwrite/appwrite/pull/10417) +* Allow running tests with specific response format in [#10418](https://github.com/appwrite/appwrite/pull/10418) +* Make webhooks publisher overridable in [#10419](https://github.com/appwrite/appwrite/pull/10419) +* Check audits logs in [#10414](https://github.com/appwrite/appwrite/pull/10414) +* Remove direct publisher calls in [#10420](https://github.com/appwrite/appwrite/pull/10420) +* removed spatial type response and will be using the json type for the… in [#10433](https://github.com/appwrite/appwrite/pull/10433) +* Add tests for new time helpers in [#10437](https://github.com/appwrite/appwrite/pull/10437) +* Move projects.list() to module in [#10441](https://github.com/appwrite/appwrite/pull/10441) +* Update cli to 9.1.0 in [#10442](https://github.com/appwrite/appwrite/pull/10442) +* Add requestBody param examples in specs in [#10431](https://github.com/appwrite/appwrite/pull/10431) +* Fix mysql tests in [#10445](https://github.com/appwrite/appwrite/pull/10445) +* Upgrade platform lib to have older queue lib in [#10447](https://github.com/appwrite/appwrite/pull/10447) +* Fix router compression in [#10452](https://github.com/appwrite/appwrite/pull/10452) +* Upgrade http lib for backwards compatible default param in [#10455](https://github.com/appwrite/appwrite/pull/10455) +* Update examples in [#10444](https://github.com/appwrite/appwrite/pull/10444) +* Automatic pr creation in sdk release script in [#10457](https://github.com/appwrite/appwrite/pull/10457) +* Remove avatars command from cli in [#10454](https://github.com/appwrite/appwrite/pull/10454) +* Remove deno from platforms array in [#10453](https://github.com/appwrite/appwrite/pull/10453) +* Spatial type attributes sdk updates in [#10463](https://github.com/appwrite/appwrite/pull/10463) +* Stats resources try catch in [#10469](https://github.com/appwrite/appwrite/pull/10469) +* Move proxy endpoints to modules in [#10470](https://github.com/appwrite/appwrite/pull/10470) +* Add certificate validation override in [#10471](https://github.com/appwrite/appwrite/pull/10471) +* Generate SDKs in [#10475](https://github.com/appwrite/appwrite/pull/10475) +* Spatial test tablesdb updates in [#10473](https://github.com/appwrite/appwrite/pull/10473) +* Add colors to certificate logs in [#10438](https://github.com/appwrite/appwrite/pull/10438) +* appwrite db bump in [#10479](https://github.com/appwrite/appwrite/pull/10479) +* Bump database in [#10480](https://github.com/appwrite/appwrite/pull/10480) +* Health db queues in [#10482](https://github.com/appwrite/appwrite/pull/10482) +* Attempt small size for website dependency in [#10485](https://github.com/appwrite/appwrite/pull/10485) +* Worker stop in [#10498](https://github.com/appwrite/appwrite/pull/10498) +* Update database in [#10506](https://github.com/appwrite/appwrite/pull/10506) +* Stats resources and usage sorting by unique field in [#10472](https://github.com/appwrite/appwrite/pull/10472) +* Add spatial column validation during required mode and tests for exis… in [#10509](https://github.com/appwrite/appwrite/pull/10509) +* Sub query variables order by in [#10513](https://github.com/appwrite/appwrite/pull/10513) +* Update README.md in [#10514](https://github.com/appwrite/appwrite/pull/10514) +* bump database 1.5.0 in [#10515](https://github.com/appwrite/appwrite/pull/10515) +* Don't remove required attributes in [#10516](https://github.com/appwrite/appwrite/pull/10516) +* Catch query exception on bulk update/delete in [#10517](https://github.com/appwrite/appwrite/pull/10517) +* Update cli to 10.0.0 in [#10511](https://github.com/appwrite/appwrite/pull/10511) +* Add type_enum support and update docs in [#10496](https://github.com/appwrite/appwrite/pull/10496) +* Improve code readability for schedules in [#10522](https://github.com/appwrite/appwrite/pull/10522) +* Include response model enum names in [#10538](https://github.com/appwrite/appwrite/pull/10538) +* SDK releases in [#10539](https://github.com/appwrite/appwrite/pull/10539) +* Fix health status enum in [#10540](https://github.com/appwrite/appwrite/pull/10540) +* Update afterbuild fn in [#10541](https://github.com/appwrite/appwrite/pull/10541) +* Update afterbuild to also pass adapter in [#10545](https://github.com/appwrite/appwrite/pull/10545) +* Update `z-index` to be the highest in [#9874](https://github.com/appwrite/appwrite/pull/9874) +* Update framework lib to 0.33.28 in [#10551](https://github.com/appwrite/appwrite/pull/10551) +* Fix enum typing for platform in specs in [#10553](https://github.com/appwrite/appwrite/pull/10553) +* Add enums for database type and column status in [#10561](https://github.com/appwrite/appwrite/pull/10561) +* Fix activities in [#10586](https://github.com/appwrite/appwrite/pull/10586) +* Fix logs truncation tests in [#10585](https://github.com/appwrite/appwrite/pull/10585) +* Remove related data in realtime payload in [#10590](https://github.com/appwrite/appwrite/pull/10590) +* Update composer dependencies in [#10601](https://github.com/appwrite/appwrite/pull/10601) +* Update sdks add response models in [#10554](https://github.com/appwrite/appwrite/pull/10554) +* Sanitize 5xx errors on realtime in [#10598](https://github.com/appwrite/appwrite/pull/10598) +* Update database in [#10596](https://github.com/appwrite/appwrite/pull/10596) +* Add both collection and table id in the realtime in [#10608](https://github.com/appwrite/appwrite/pull/10608) +* Chore bump db in [#10611](https://github.com/appwrite/appwrite/pull/10611) +* Branded email for Console auth flows in [#10501](https://github.com/appwrite/appwrite/pull/10501) +* Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification in [#10614](https://github.com/appwrite/appwrite/pull/10614) +* Add automatic releases in [#10615](https://github.com/appwrite/appwrite/pull/10615) +* Feat txn sdks in [#10621](https://github.com/appwrite/appwrite/pull/10621) +* Prevent empty releases in sdk release script in [#10627](https://github.com/appwrite/appwrite/pull/10627) +* Update domains lib to 0.8.2 in [#10629](https://github.com/appwrite/appwrite/pull/10629) +* Fix txn API scope backwards compat in [#10640](https://github.com/appwrite/appwrite/pull/10640) +* Fix block schedules in [#10620](https://github.com/appwrite/appwrite/pull/10620) +* Update .NET SDK to 0.21.2 and improve release detection in [#10641](https://github.com/appwrite/appwrite/pull/10641) +* Make methods protected for extending in [#10617](https://github.com/appwrite/appwrite/pull/10617) # Version 1.7.4 @@ -345,15 +345,15 @@ ### Notable changes -* Update console image to version 6.0.13 by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9891 +* Update console image to version 6.0.13 in [#9891](https://github.com/appwrite/appwrite/pull/9891) ### Fixes -* Fix createDeployment chunk upload by @Meldiron in https://github.com/appwrite/appwrite/pull/9886 +* Fix createDeployment chunk upload in [#9886](https://github.com/appwrite/appwrite/pull/9886) ### Miscellaneous -* Update version from 1.7.3 to 1.7.4 by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9893 +* Update version from 1.7.3 to 1.7.4 in [#9893](https://github.com/appwrite/appwrite/pull/9893) # Version 1.7.3 @@ -361,30 +361,30 @@ ### Notable changes -* Allow unlimited deployment size by @Meldiron in https://github.com/appwrite/appwrite/pull/9866 -* Bump console to version 6.0.11 by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9881 +* Allow unlimited deployment size in [#9866](https://github.com/appwrite/appwrite/pull/9866) +* Bump console to version 6.0.11 in [#9881](https://github.com/appwrite/appwrite/pull/9881) ### Fixes -* Send deploymentResourceType in rules verification by @basert in https://github.com/appwrite/appwrite/pull/9859 -* Fix CNAME validation by @Meldiron in https://github.com/appwrite/appwrite/pull/9861 -* Fix bucket not included in path by @abnegate in https://github.com/appwrite/appwrite/pull/9864 -* Fix URL for view logs in github comment by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9875 -* Set owner and region while migrating rules by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9856 -* Remove _APP_DEFAULT_REGION because it is not a valid env var by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9883 +* Send deploymentResourceType in rules verification in [#9859](https://github.com/appwrite/appwrite/pull/9859) +* Fix CNAME validation in [#9861](https://github.com/appwrite/appwrite/pull/9861) +* Fix bucket not included in path in [#9864](https://github.com/appwrite/appwrite/pull/9864) +* Fix URL for view logs in github comment in [#9875](https://github.com/appwrite/appwrite/pull/9875) +* Set owner and region while migrating rules in [#9856](https://github.com/appwrite/appwrite/pull/9856) +* Remove _APP_DEFAULT_REGION because it is not a valid env var in [#9883](https://github.com/appwrite/appwrite/pull/9883) ### Miscellaneous -* Only load error page for development mode by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9860 -* Make max deployment and build size configurable by @vermakhushboo in https://github.com/appwrite/appwrite/pull/9863 -* Update flutter_web_auth_2 docs to match 4.x by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9858 -* Use unique filename for health check by @ChiragAgg5k in https://github.com/appwrite/appwrite/pull/9842 -* Added encrypt property in the attribute string response model by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9868 -* Add sequence by @abnegate in https://github.com/appwrite/appwrite/pull/9865 -* Add builds worker group by @loks0n in https://github.com/appwrite/appwrite/pull/9873 -* updated errro for the string encryption by @ArnabChatterjee20k in https://github.com/appwrite/appwrite/pull/9878 -* Revert "Add sequence" by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9879 -* Prepare 1.7.3 release by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9882 +* Only load error page for development mode in [#9860](https://github.com/appwrite/appwrite/pull/9860) +* Make max deployment and build size configurable in [#9863](https://github.com/appwrite/appwrite/pull/9863) +* Update flutter_web_auth_2 docs to match 4.x in [#9858](https://github.com/appwrite/appwrite/pull/9858) +* Use unique filename for health check in [#9842](https://github.com/appwrite/appwrite/pull/9842) +* Added encrypt property in the attribute string response model in [#9868](https://github.com/appwrite/appwrite/pull/9868) +* Add sequence in [#9865](https://github.com/appwrite/appwrite/pull/9865) +* Add builds worker group in [#9873](https://github.com/appwrite/appwrite/pull/9873) +* updated errro for the string encryption in [#9878](https://github.com/appwrite/appwrite/pull/9878) +* Revert "Add sequence" in [#9879](https://github.com/appwrite/appwrite/pull/9879) +* Prepare 1.7.3 release in [#9882](https://github.com/appwrite/appwrite/pull/9882) # Version 1.6.2 @@ -392,263 +392,263 @@ ### Notable changes -* Delete git folder to reduce build size in [9076](https://github.com/appwrite/appwrite/pull/9076) -* Upgrade assistant in [9100](https://github.com/appwrite/appwrite/pull/9100) -* Use redis adapter for abuse in [9121](https://github.com/appwrite/appwrite/pull/9121) -* Set base specification CPUs to 0.5 again in [9146](https://github.com/appwrite/appwrite/pull/9146) -* Add new push message parameters in [9060](https://github.com/appwrite/appwrite/pull/9060) -* Update audits to include user type in [9211](https://github.com/appwrite/appwrite/pull/9211) -* Enable HEIC in [9251](https://github.com/appwrite/appwrite/pull/9251) -* Added teamName to membership redirect url in [9269](https://github.com/appwrite/appwrite/pull/9269) -* Add support endpoint url for S3 in [9303](https://github.com/appwrite/appwrite/pull/9303) -* Added RuPay Credit Card Icon in Avatars Service in [5046](https://github.com/appwrite/appwrite/pull/5046) -* Add figma oauth provider in [9623](https://github.com/appwrite/appwrite/pull/9623) -* Update console to version 5.2.58 in [9637](https://github.com/appwrite/appwrite/pull/9637) +* Delete git folder to reduce build size in [#9076](https://github.com/appwrite/appwrite/pull/9076) +* Upgrade assistant in [#9100](https://github.com/appwrite/appwrite/pull/9100) +* Use redis adapter for abuse in [#9121](https://github.com/appwrite/appwrite/pull/9121) +* Set base specification CPUs to 0.5 again in [#9146](https://github.com/appwrite/appwrite/pull/9146) +* Add new push message parameters in [#9060](https://github.com/appwrite/appwrite/pull/9060) +* Update audits to include user type in [#9211](https://github.com/appwrite/appwrite/pull/9211) +* Enable HEIC in [#9251](https://github.com/appwrite/appwrite/pull/9251) +* Added teamName to membership redirect url in [#9269](https://github.com/appwrite/appwrite/pull/9269) +* Add support endpoint url for S3 in [#9303](https://github.com/appwrite/appwrite/pull/9303) +* Added RuPay Credit Card Icon in Avatars Service in [#5046](https://github.com/appwrite/appwrite/pull/5046) +* Add figma oauth provider in [#9623](https://github.com/appwrite/appwrite/pull/9623) +* Update console to version 5.2.58 in [#9637](https://github.com/appwrite/appwrite/pull/9637) ### Fixes -* Remove failed attribute in [9032](https://github.com/appwrite/appwrite/pull/9032) -* Fix delete notFound attribute in [9038](https://github.com/appwrite/appwrite/pull/9038) -* 🇮🇸 Added missing Icelandic translations for email strings. in [4848](https://github.com/appwrite/appwrite/pull/4848) -* fix doc comment for filter method in [5769](https://github.com/appwrite/appwrite/pull/5769) -* Delete attribute No throwing Exception on not found in [9157](https://github.com/appwrite/appwrite/pull/9157) -* Fix VCS identity collision in [9138](https://github.com/appwrite/appwrite/pull/9138) -* Fix disabling of email-otp when user wants to in [9200](https://github.com/appwrite/appwrite/pull/9200) -* Ensure user can delete session in [9209](https://github.com/appwrite/appwrite/pull/9209) -* Fix resend invitation in [9218](https://github.com/appwrite/appwrite/pull/9218) -* Fix phone number parsing exception handling in [9246](https://github.com/appwrite/appwrite/pull/9246) -* Fix amazon oauth in [9253](https://github.com/appwrite/appwrite/pull/9253) -* Fix slack oauth scopes, and updated to v2 in [9228](https://github.com/appwrite/appwrite/pull/9228) -* Fix forwarded user agent in [9271](https://github.com/appwrite/appwrite/pull/9271) -* Fix WEBP File Preview Rendering Issue in [9321](https://github.com/appwrite/appwrite/pull/9321) -* Fix build memory specifications in [9360](https://github.com/appwrite/appwrite/pull/9360) -* Fix Self Hosting functions by adding missed config in [9373](https://github.com/appwrite/appwrite/pull/9373) -* Fix resend team invite if already accepted in [9348](https://github.com/appwrite/appwrite/pull/9348) -* Fix null errors on team invite in [9391](https://github.com/appwrite/appwrite/pull/9391) -* Fix email (smtp) to multiple recipients in [9243](https://github.com/appwrite/appwrite/pull/9243) -* Fix stats timing by using receivedAt date when available in [9428](https://github.com/appwrite/appwrite/pull/9428) -* Make min/max params optional for attribute update in [9387](https://github.com/appwrite/appwrite/pull/9387) -* Fix blocking of phone sessions when disabled on console in [9447](https://github.com/appwrite/appwrite/pull/9447) -* Fix logging config in [9467](https://github.com/appwrite/appwrite/pull/9467) -* Update audit timestamp origin in [9481](https://github.com/appwrite/appwrite/pull/9481) -* Fix certificates in deletes worker in [9466](https://github.com/appwrite/appwrite/pull/9466) -* Fix console audits delete in [9547](https://github.com/appwrite/appwrite/pull/9547) -* Fix migrations in [9633](https://github.com/appwrite/appwrite/pull/9633) -* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [9679](https://github.com/appwrite/appwrite/pull/9679) -* Treat 0 as unlimited for CPUs and memory in [9638](https://github.com/appwrite/appwrite/pull/9638) -* Add contextual dispatch logic to fix high CPU usage in [9687](https://github.com/appwrite/appwrite/pull/9687) +* Remove failed attribute in [#9032](https://github.com/appwrite/appwrite/pull/9032) +* Fix delete notFound attribute in [#9038](https://github.com/appwrite/appwrite/pull/9038) +* 🇮🇸 Added missing Icelandic translations for email strings. in [#4848](https://github.com/appwrite/appwrite/pull/4848) +* fix doc comment for filter method in [#5769](https://github.com/appwrite/appwrite/pull/5769) +* Delete attribute No throwing Exception on not found in [#9157](https://github.com/appwrite/appwrite/pull/9157) +* Fix VCS identity collision in [#9138](https://github.com/appwrite/appwrite/pull/9138) +* Fix disabling of email-otp when user wants to in [#9200](https://github.com/appwrite/appwrite/pull/9200) +* Ensure user can delete session in [#9209](https://github.com/appwrite/appwrite/pull/9209) +* Fix resend invitation in [#9218](https://github.com/appwrite/appwrite/pull/9218) +* Fix phone number parsing exception handling in [#9246](https://github.com/appwrite/appwrite/pull/9246) +* Fix amazon oauth in [#9253](https://github.com/appwrite/appwrite/pull/9253) +* Fix slack oauth scopes, and updated to v2 in [#9228](https://github.com/appwrite/appwrite/pull/9228) +* Fix forwarded user agent in [#9271](https://github.com/appwrite/appwrite/pull/9271) +* Fix WEBP File Preview Rendering Issue in [#9321](https://github.com/appwrite/appwrite/pull/9321) +* Fix build memory specifications in [#9360](https://github.com/appwrite/appwrite/pull/9360) +* Fix Self Hosting functions by adding missed config in [#9373](https://github.com/appwrite/appwrite/pull/9373) +* Fix resend team invite if already accepted in [#9348](https://github.com/appwrite/appwrite/pull/9348) +* Fix null errors on team invite in [#9391](https://github.com/appwrite/appwrite/pull/9391) +* Fix email (smtp) to multiple recipients in [#9243](https://github.com/appwrite/appwrite/pull/9243) +* Fix stats timing by using receivedAt date when available in [#9428](https://github.com/appwrite/appwrite/pull/9428) +* Make min/max params optional for attribute update in [#9387](https://github.com/appwrite/appwrite/pull/9387) +* Fix blocking of phone sessions when disabled on console in [#9447](https://github.com/appwrite/appwrite/pull/9447) +* Fix logging config in [#9467](https://github.com/appwrite/appwrite/pull/9467) +* Update audit timestamp origin in [#9481](https://github.com/appwrite/appwrite/pull/9481) +* Fix certificates in deletes worker in [#9466](https://github.com/appwrite/appwrite/pull/9466) +* Fix console audits delete in [#9547](https://github.com/appwrite/appwrite/pull/9547) +* Fix migrations in [#9633](https://github.com/appwrite/appwrite/pull/9633) +* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [#9679](https://github.com/appwrite/appwrite/pull/9679) +* Treat 0 as unlimited for CPUs and memory in [#9638](https://github.com/appwrite/appwrite/pull/9638) +* Add contextual dispatch logic to fix high CPU usage in [#9687](https://github.com/appwrite/appwrite/pull/9687) ### Miscellaneous -* Merge 1.6.x into feat-custom-cf-hostnames in [8904](https://github.com/appwrite/appwrite/pull/8904) -* Improve compression param checks in [8922](https://github.com/appwrite/appwrite/pull/8922) -* upgrade utopia storage in [8930](https://github.com/appwrite/appwrite/pull/8930) -* Feat migration in [8797](https://github.com/appwrite/appwrite/pull/8797) -* feat fix web routes in [8962](https://github.com/appwrite/appwrite/pull/8962) -* Fix no pool access in [9027](https://github.com/appwrite/appwrite/pull/9027) -* feat: use environment variable to check rules format in [9039](https://github.com/appwrite/appwrite/pull/9039) -* Update storage.php in [9037](https://github.com/appwrite/appwrite/pull/9037) -* Upgrade db 0.53.200 in [9050](https://github.com/appwrite/appwrite/pull/9050) -* Chore: upgrade utopia storage in [9066](https://github.com/appwrite/appwrite/pull/9066) -* Update usage-dump payload in [9085](https://github.com/appwrite/appwrite/pull/9085) -* GitHub Workflows security hardening in [3728](https://github.com/appwrite/appwrite/pull/3728) -* Update add-oauth2-provider.md in [4313](https://github.com/appwrite/appwrite/pull/4313) -* update readme-cn some doc in [5278](https://github.com/appwrite/appwrite/pull/5278) -* Add accessibility features in [7042](https://github.com/appwrite/appwrite/pull/7042) -* Add Appwrite Cloud to read me. in [5445](https://github.com/appwrite/appwrite/pull/5445) -* Migration throw error in [9092](https://github.com/appwrite/appwrite/pull/9092) -* Fix usage payload bug in [9097](https://github.com/appwrite/appwrite/pull/9097) -* chore: replace occurrences of dbForConsole to dbForPlatform in [9096](https://github.com/appwrite/appwrite/pull/9096) -* fix(realtime): decrement connectionCounter only if connection is known in [9055](https://github.com/appwrite/appwrite/pull/9055) -* payload bug fix in [9098](https://github.com/appwrite/appwrite/pull/9098) -* Fix usage payload bug in [9099](https://github.com/appwrite/appwrite/pull/9099) -* Usage payload debug in [9101](https://github.com/appwrite/appwrite/pull/9101) -* Usage payload debug in [9103](https://github.com/appwrite/appwrite/pull/9103) -* Usage payload debug in [9104](https://github.com/appwrite/appwrite/pull/9104) -* Feat: createFunction abuse labels in [9102](https://github.com/appwrite/appwrite/pull/9102) -* Docs-create-document in [9105](https://github.com/appwrite/appwrite/pull/9105) -* Docs: Create document and unknown attribute error messages. in [5427](https://github.com/appwrite/appwrite/pull/5427) -* Fix: update project accessed at from router and schedulers in [9109](https://github.com/appwrite/appwrite/pull/9109) -* chore: initial commit in [9111](https://github.com/appwrite/appwrite/pull/9111) -* chore: optimise webhooks payload in [9115](https://github.com/appwrite/appwrite/pull/9115) -* Revert "chore: initial commit" in [9117](https://github.com/appwrite/appwrite/pull/9117) -* chore: fix attribute name in [9118](https://github.com/appwrite/appwrite/pull/9118) -* Migrate to redis abuse in [9124](https://github.com/appwrite/appwrite/pull/9124) -* Added webhooks usage stats in [9125](https://github.com/appwrite/appwrite/pull/9125) -* chore remove abuse cleanup in [9137](https://github.com/appwrite/appwrite/pull/9137) -* fix: remove abuse delete trigger in [9139](https://github.com/appwrite/appwrite/pull/9139) -* Remove firebase OAuth API endpoints in [9144](https://github.com/appwrite/appwrite/pull/9144) -* chore: release client sdks in [9112](https://github.com/appwrite/appwrite/pull/9112) -* Update general.php in [9155](https://github.com/appwrite/appwrite/pull/9155) -* feat(swoole): allow configuration override of available cpus in [9177](https://github.com/appwrite/appwrite/pull/9177) -* Usage databases api read writes addition in [9142](https://github.com/appwrite/appwrite/pull/9142) -* Fix dead connections in [9190](https://github.com/appwrite/appwrite/pull/9190) -* Add hostname to audits in [9165](https://github.com/appwrite/appwrite/pull/9165) -* chore: shifted authphone usage tracking to api calls in [9191](https://github.com/appwrite/appwrite/pull/9191) -* Revert "Fix dead connections" in [9201](https://github.com/appwrite/appwrite/pull/9201) -* Add assertEventually to messaging provider logs test in [9192](https://github.com/appwrite/appwrite/pull/9192) -* feat project sms usage in [9198](https://github.com/appwrite/appwrite/pull/9198) -* chore: add audit labels to project resources in [9056](https://github.com/appwrite/appwrite/pull/9056) -* fix sms usage in [9207](https://github.com/appwrite/appwrite/pull/9207) -* Update database in [9202](https://github.com/appwrite/appwrite/pull/9202) -* Fix dead connections in [9213](https://github.com/appwrite/appwrite/pull/9213) -* Revert "Fix dead connections" in [9214](https://github.com/appwrite/appwrite/pull/9214) -* Add logs db init for consistency in [9163](https://github.com/appwrite/appwrite/pull/9163) -* Split the collection definitions in [9153](https://github.com/appwrite/appwrite/pull/9153) -* Log path with populated parameters in [9220](https://github.com/appwrite/appwrite/pull/9220) -* Add missing scope on function template in [9208](https://github.com/appwrite/appwrite/pull/9208) -* Add relatedCollection default in [9225](https://github.com/appwrite/appwrite/pull/9225) -* fix: function usage in [9235](https://github.com/appwrite/appwrite/pull/9235) -* feat: optimise events payloads in [9232](https://github.com/appwrite/appwrite/pull/9232) -* Optimise webhook events in [9168](https://github.com/appwrite/appwrite/pull/9168) -* fix: maintenance job missing type in [9238](https://github.com/appwrite/appwrite/pull/9238) -* Update Fetch to 0.3.0 in [9245](https://github.com/appwrite/appwrite/pull/9245) -* Fix maintenance job in [9247](https://github.com/appwrite/appwrite/pull/9247) -* chore: add missing case for executions in [9248](https://github.com/appwrite/appwrite/pull/9248) -* Add index dependency exception in [9226](https://github.com/appwrite/appwrite/pull/9226) -* chore: fix benchmarking test when made from fork in [9233](https://github.com/appwrite/appwrite/pull/9233) -* Update SDK Generator versions in [9188](https://github.com/appwrite/appwrite/pull/9188) -* chore: skipped job instead of throwing error in [9250](https://github.com/appwrite/appwrite/pull/9250) -* Implement new SDK Class on 1.6.x in [9237](https://github.com/appwrite/appwrite/pull/9237) -* Delete collection before Appwrite's attributes in [9256](https://github.com/appwrite/appwrite/pull/9256) -* Feat batch usage dump in [9255](https://github.com/appwrite/appwrite/pull/9255) -* Fix cloud tests in [9261](https://github.com/appwrite/appwrite/pull/9261) -* Usage: Databases reads writes in [9260](https://github.com/appwrite/appwrite/pull/9260) -* Update: Latest sdk specs in [9274](https://github.com/appwrite/appwrite/pull/9274) -* Revert "Feat batch usage dump" in [9276](https://github.com/appwrite/appwrite/pull/9276) -* feat: add fast2SMS adapter in [9263](https://github.com/appwrite/appwrite/pull/9263) -* Update Sdk Generator dependency in [9280](https://github.com/appwrite/appwrite/pull/9280) -* Transformed at addition in [9281](https://github.com/appwrite/appwrite/pull/9281) -* Docs: clarify update endpoints only work on draft messages in [9236](https://github.com/appwrite/appwrite/pull/9236) -* Update sdk generator dependency in [9282](https://github.com/appwrite/appwrite/pull/9282) -* Revert "Transformed at addition" in [9284](https://github.com/appwrite/appwrite/pull/9284) -* replaced init for cloud link in [9285](https://github.com/appwrite/appwrite/pull/9285) -* Add transformed at in [9289](https://github.com/appwrite/appwrite/pull/9289) -* Make migrations use Dynamic keys for destination in [9291](https://github.com/appwrite/appwrite/pull/9291) -* Make sessions limit tests assert eventually in [9298](https://github.com/appwrite/appwrite/pull/9298) -* Chore update database in [9306](https://github.com/appwrite/appwrite/pull/9306) -* feat: add AMQP queues in [9287](https://github.com/appwrite/appwrite/pull/9287) -* fix(test): use assertEventually instead of while(true) in [9308](https://github.com/appwrite/appwrite/pull/9308) -* fix(certificate worker): events are published without queue name in [9309](https://github.com/appwrite/appwrite/pull/9309) -* chore: update utopia-php/queue to 0.8.1 in [9311](https://github.com/appwrite/appwrite/pull/9311) -* chore: update utopia-php/queue to 0.8.2 in [9312](https://github.com/appwrite/appwrite/pull/9312) -* fix(schedule-tasks): revert back to direct pool usage in [9313](https://github.com/appwrite/appwrite/pull/9313) -* feat: custom app schemes in [9262](https://github.com/appwrite/appwrite/pull/9262) -* Revert "feat: custom app schemes" in [9319](https://github.com/appwrite/appwrite/pull/9319) -* Restore "feat: custom app schemes"" in [9320](https://github.com/appwrite/appwrite/pull/9320) -* Revert "Restore "feat: custom app schemes""" in [9323](https://github.com/appwrite/appwrite/pull/9323) -* chore: update dependencies in [9330](https://github.com/appwrite/appwrite/pull/9330) -* Feat: logs DB in [9272](https://github.com/appwrite/appwrite/pull/9272) -* Catch invalid index in [9329](https://github.com/appwrite/appwrite/pull/9329) -* Fix: missing call for image transformations counting in [9342](https://github.com/appwrite/appwrite/pull/9342) -* Fix drop abuse on shared table project delete in [9346](https://github.com/appwrite/appwrite/pull/9346) -* Only run all table mode tests on db update in [9338](https://github.com/appwrite/appwrite/pull/9338) -* Fix: missing periodic metric in [9350](https://github.com/appwrite/appwrite/pull/9350) -* feat(builds): check if function is blocked before building in [9332](https://github.com/appwrite/appwrite/pull/9332) -* feat: batch create audit logs in [9347](https://github.com/appwrite/appwrite/pull/9347) -* Chore: Update migrations in [9355](https://github.com/appwrite/appwrite/pull/9355) -* Fix: metric time was not being written to DB in [9354](https://github.com/appwrite/appwrite/pull/9354) -* Fix patch index validation in [9356](https://github.com/appwrite/appwrite/pull/9356) -* Fix image trnasformation metrics in [9370](https://github.com/appwrite/appwrite/pull/9370) -* Use batch delete in worker in [9375](https://github.com/appwrite/appwrite/pull/9375) -* Fix Model Platform is missing response key: store in [9361](https://github.com/appwrite/appwrite/pull/9361) -* Feat key segmented usage in [9336](https://github.com/appwrite/appwrite/pull/9336) -* Feat messaging metrics in [9353](https://github.com/appwrite/appwrite/pull/9353) -* Fix removed audits for shared v2 in [9388](https://github.com/appwrite/appwrite/pull/9388) -* chore: bump utopia-php/image to 0.8.0 in [9390](https://github.com/appwrite/appwrite/pull/9390) -* Fix outdated CLI commands in documentation in [9122](https://github.com/appwrite/appwrite/pull/9122) -* disable logs display in [9398](https://github.com/appwrite/appwrite/pull/9398) -* Log batches per project in [9403](https://github.com/appwrite/appwrite/pull/9403) -* Batch per project in [9410](https://github.com/appwrite/appwrite/pull/9410) -* Fix: stats resources only queue projects accessed in last 3 hours in [9411](https://github.com/appwrite/appwrite/pull/9411) -* Track options requests in [9397](https://github.com/appwrite/appwrite/pull/9397) -* chore: bump docker-base in [9406](https://github.com/appwrite/appwrite/pull/9406) -* refactor: migrate Realtime::send calls to queueForRealtime in [9325](https://github.com/appwrite/appwrite/pull/9325) -* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [9424](https://github.com/appwrite/appwrite/pull/9424) -* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [9339](https://github.com/appwrite/appwrite/pull/9339) -* Fix: disable dual writing in [9429](https://github.com/appwrite/appwrite/pull/9429) -* Disable transformedAt update for console users in [9425](https://github.com/appwrite/appwrite/pull/9425) -* chore: add image transformation stats to usage endpoint in [9393](https://github.com/appwrite/appwrite/pull/9393) -* chore: added timeout to deployment builds in tests in [9426](https://github.com/appwrite/appwrite/pull/9426) -* fix: model for image transformations in usage project in [9442](https://github.com/appwrite/appwrite/pull/9442) -* Feat: calculate database storage in stats-resources in [9443](https://github.com/appwrite/appwrite/pull/9443) -* Activities batch writes in [9438](https://github.com/appwrite/appwrite/pull/9438) -* chore: bump cache 0.12.x in [9412](https://github.com/appwrite/appwrite/pull/9412) -* chore: queue console project for maintenance delete in [9479](https://github.com/appwrite/appwrite/pull/9479) -* chore: added logsdb for deletes worker in [9462](https://github.com/appwrite/appwrite/pull/9462) -* Feat: calculate and log time taken for each project in [9491](https://github.com/appwrite/appwrite/pull/9491) -* chore: update initializing dbForLogs in [9494](https://github.com/appwrite/appwrite/pull/9494) -* Feat bulk audit delete in [9487](https://github.com/appwrite/appwrite/pull/9487) -* Prepare 1.6.2 release in [9499](https://github.com/appwrite/appwrite/pull/9499) -* Regenerate specs in [9497](https://github.com/appwrite/appwrite/pull/9497) -* Regenerate examples in [9498](https://github.com/appwrite/appwrite/pull/9498) -* chore: bump sdk in [9414](https://github.com/appwrite/appwrite/pull/9414) -* update queue to 0.9.* in [9505](https://github.com/appwrite/appwrite/pull/9505) -* Feat improve delete queries in [9507](https://github.com/appwrite/appwrite/pull/9507) -* Feat: Add rule attributes in [9508](https://github.com/appwrite/appwrite/pull/9508) -* Sync main into 1.6.x in [9496](https://github.com/appwrite/appwrite/pull/9496) -* Bump console to version 5.2.53 in [9495](https://github.com/appwrite/appwrite/pull/9495) -* Prepare 1.6.1 release in [9294](https://github.com/appwrite/appwrite/pull/9294) -* Improve delete ordering in [9512](https://github.com/appwrite/appwrite/pull/9512) -* Cleanups in [9511](https://github.com/appwrite/appwrite/pull/9511) -* Feat dynamic regions in [9408](https://github.com/appwrite/appwrite/pull/9408) -* Feat env vars to system lib in [9515](https://github.com/appwrite/appwrite/pull/9515) -* Feat: domains count in [9514](https://github.com/appwrite/appwrite/pull/9514) -* Migration read from db in [9529](https://github.com/appwrite/appwrite/pull/9529) -* feat: add pool telemetry in [9530](https://github.com/appwrite/appwrite/pull/9530) -* Disable PDO persistence since we manage our own pool in [9526](https://github.com/appwrite/appwrite/pull/9526) -* chore: set min operations to 1 for reads and writes in [9536](https://github.com/appwrite/appwrite/pull/9536) -* Remove default region in [9430](https://github.com/appwrite/appwrite/pull/9430) -* Use cursor pagination with bigger limit for maintenance project loop in [9546](https://github.com/appwrite/appwrite/pull/9546) -* chore: stop tests on failure in [9525](https://github.com/appwrite/appwrite/pull/9525) -* chore: only update total count for privileged users in [9554](https://github.com/appwrite/appwrite/pull/9554) -* refactor: initialization of audit retention in [9563](https://github.com/appwrite/appwrite/pull/9563) -* Delete worker queries fixes in [9523](https://github.com/appwrite/appwrite/pull/9523) -* Bump database 0.62.x in [9568](https://github.com/appwrite/appwrite/pull/9568) -* Fix: schedules region filtering in [9577](https://github.com/appwrite/appwrite/pull/9577) -* Deletes worker fix selects for pagination in [9578](https://github.com/appwrite/appwrite/pull/9578) -* Add $permissions for delete documents selects in [9579](https://github.com/appwrite/appwrite/pull/9579) -* chore(audits): return queue pre-fetch results in [9533](https://github.com/appwrite/appwrite/pull/9533) -* Revert "chore(audits): return queue pre-fetch results" in [9586](https://github.com/appwrite/appwrite/pull/9586) -* Feat multi tenant insert in [9573](https://github.com/appwrite/appwrite/pull/9573) -* Add order by for cursor in [9588](https://github.com/appwrite/appwrite/pull/9588) -* Feat update fetch in [9592](https://github.com/appwrite/appwrite/pull/9592) -* Fix tenant casting in [9598](https://github.com/appwrite/appwrite/pull/9598) -* Feat update ws in [9602](https://github.com/appwrite/appwrite/pull/9602) -* Update database in [9603](https://github.com/appwrite/appwrite/pull/9603) -* Fix: image transformation cache in [9608](https://github.com/appwrite/appwrite/pull/9608) -* Remove audit payload in [9610](https://github.com/appwrite/appwrite/pull/9610) -* Sample rate from DSN in [9559](https://github.com/appwrite/appwrite/pull/9559) -* Restrict role change for sole org owner in [9615](https://github.com/appwrite/appwrite/pull/9615) -* chore: update php image to 0.8.1 in [9616](https://github.com/appwrite/appwrite/pull/9616) -* feat: refactor executor setup in [9420](https://github.com/appwrite/appwrite/pull/9420) -* chore: update gitpod.yml config in [9561](https://github.com/appwrite/appwrite/pull/9561) -* chore: update dependencies in [9625](https://github.com/appwrite/appwrite/pull/9625) -* Update migrations lib in [9628](https://github.com/appwrite/appwrite/pull/9628) -* feat: cache telemetry in [9624](https://github.com/appwrite/appwrite/pull/9624) -* Bump console to version 5.2.56 in [9631](https://github.com/appwrite/appwrite/pull/9631) -* Multi region support in [8667](https://github.com/appwrite/appwrite/pull/8667) -* Revert "Multi region support" in [9632](https://github.com/appwrite/appwrite/pull/9632) -* Revert "Revert "Multi region support"" in [9636](https://github.com/appwrite/appwrite/pull/9636) -* Fix tasks in [9644](https://github.com/appwrite/appwrite/pull/9644) -* chore: updated the migration version to 8.6 in [9646](https://github.com/appwrite/appwrite/pull/9646) -* Fix: merge the working of StatsUsage and StatsUsageDump in [9585](https://github.com/appwrite/appwrite/pull/9585) -* Update database in [9643](https://github.com/appwrite/appwrite/pull/9643) -* chore: fix error logging for CLI tasks in [9651](https://github.com/appwrite/appwrite/pull/9651) -* fix: usage test assertion in [9653](https://github.com/appwrite/appwrite/pull/9653) -* Fix keys in [9656](https://github.com/appwrite/appwrite/pull/9656) -* Feat: multi tenant dual writing in [9583](https://github.com/appwrite/appwrite/pull/9583) -* Fix/throwing 400 for null order attributes in [9657](https://github.com/appwrite/appwrite/pull/9657) -* feat: sdk group attribute in [9596](https://github.com/appwrite/appwrite/pull/9596) -* Add configurable function and build size in [9648](https://github.com/appwrite/appwrite/pull/9648) -* feat: update API endpoint in the code examples in [8933](https://github.com/appwrite/appwrite/pull/8933) -* chore: abstract token secret hiding to response model in [9574](https://github.com/appwrite/appwrite/pull/9574) -* chore: update sdks in [9655](https://github.com/appwrite/appwrite/pull/9655) -* feat: allow non-critical events to ignore exceptions when enqueuing the event in [9680](https://github.com/appwrite/appwrite/pull/9680) -* Revert "Add configurable function and build size" in [9681](https://github.com/appwrite/appwrite/pull/9681) -* core: introduce endpoint.docs in specs in [9685](https://github.com/appwrite/appwrite/pull/9685) -* fix: remove content-type header from get request specs in [9666](https://github.com/appwrite/appwrite/pull/9666) -* chore: update flutter sdk in [9691](https://github.com/appwrite/appwrite/pull/9691) +* Merge 1.6.x into feat-custom-cf-hostnames in [#8904](https://github.com/appwrite/appwrite/pull/8904) +* Improve compression param checks in [#8922](https://github.com/appwrite/appwrite/pull/8922) +* upgrade utopia storage in [#8930](https://github.com/appwrite/appwrite/pull/8930) +* Feat migration in [#8797](https://github.com/appwrite/appwrite/pull/8797) +* feat fix web routes in [#8962](https://github.com/appwrite/appwrite/pull/8962) +* Fix no pool access in [#9027](https://github.com/appwrite/appwrite/pull/9027) +* feat: use environment variable to check rules format in [#9039](https://github.com/appwrite/appwrite/pull/9039) +* Update storage.php in [#9037](https://github.com/appwrite/appwrite/pull/9037) +* Upgrade db 0.53.200 in [#9050](https://github.com/appwrite/appwrite/pull/9050) +* Chore: upgrade utopia storage in [#9066](https://github.com/appwrite/appwrite/pull/9066) +* Update usage-dump payload in [#9085](https://github.com/appwrite/appwrite/pull/9085) +* GitHub Workflows security hardening in [#3728](https://github.com/appwrite/appwrite/pull/3728) +* Update add-oauth2-provider.md in [#4313](https://github.com/appwrite/appwrite/pull/4313) +* update readme-cn some doc in [#5278](https://github.com/appwrite/appwrite/pull/5278) +* Add accessibility features in [#7042](https://github.com/appwrite/appwrite/pull/7042) +* Add Appwrite Cloud to read me. in [#5445](https://github.com/appwrite/appwrite/pull/5445) +* Migration throw error in [#9092](https://github.com/appwrite/appwrite/pull/9092) +* Fix usage payload bug in [#9097](https://github.com/appwrite/appwrite/pull/9097) +* chore: replace occurrences of dbForConsole to dbForPlatform in [#9096](https://github.com/appwrite/appwrite/pull/9096) +* fix(realtime): decrement connectionCounter only if connection is known in [#9055](https://github.com/appwrite/appwrite/pull/9055) +* payload bug fix in [#9098](https://github.com/appwrite/appwrite/pull/9098) +* Fix usage payload bug in [#9099](https://github.com/appwrite/appwrite/pull/9099) +* Usage payload debug in [#9101](https://github.com/appwrite/appwrite/pull/9101) +* Usage payload debug in [#9103](https://github.com/appwrite/appwrite/pull/9103) +* Usage payload debug in [#9104](https://github.com/appwrite/appwrite/pull/9104) +* Feat: createFunction abuse labels in [#9102](https://github.com/appwrite/appwrite/pull/9102) +* Docs-create-document in [#9105](https://github.com/appwrite/appwrite/pull/9105) +* Docs: Create document and unknown attribute error messages. in [#5427](https://github.com/appwrite/appwrite/pull/5427) +* Fix: update project accessed at from router and schedulers in [#9109](https://github.com/appwrite/appwrite/pull/9109) +* chore: initial commit in [#9111](https://github.com/appwrite/appwrite/pull/9111) +* chore: optimise webhooks payload in [#9115](https://github.com/appwrite/appwrite/pull/9115) +* Revert "chore: initial commit" in [#9117](https://github.com/appwrite/appwrite/pull/9117) +* chore: fix attribute name in [#9118](https://github.com/appwrite/appwrite/pull/9118) +* Migrate to redis abuse in [#9124](https://github.com/appwrite/appwrite/pull/9124) +* Added webhooks usage stats in [#9125](https://github.com/appwrite/appwrite/pull/9125) +* chore remove abuse cleanup in [#9137](https://github.com/appwrite/appwrite/pull/9137) +* fix: remove abuse delete trigger in [#9139](https://github.com/appwrite/appwrite/pull/9139) +* Remove firebase OAuth API endpoints in [#9144](https://github.com/appwrite/appwrite/pull/9144) +* chore: release client sdks in [#9112](https://github.com/appwrite/appwrite/pull/9112) +* Update general.php in [#9155](https://github.com/appwrite/appwrite/pull/9155) +* feat(swoole): allow configuration override of available cpus in [#9177](https://github.com/appwrite/appwrite/pull/9177) +* Usage databases api read writes addition in [#9142](https://github.com/appwrite/appwrite/pull/9142) +* Fix dead connections in [#9190](https://github.com/appwrite/appwrite/pull/9190) +* Add hostname to audits in [#9165](https://github.com/appwrite/appwrite/pull/9165) +* chore: shifted authphone usage tracking to api calls in [#9191](https://github.com/appwrite/appwrite/pull/9191) +* Revert "Fix dead connections" in [#9201](https://github.com/appwrite/appwrite/pull/9201) +* Add assertEventually to messaging provider logs test in [#9192](https://github.com/appwrite/appwrite/pull/9192) +* feat project sms usage in [#9198](https://github.com/appwrite/appwrite/pull/9198) +* chore: add audit labels to project resources in [#9056](https://github.com/appwrite/appwrite/pull/9056) +* fix sms usage in [#9207](https://github.com/appwrite/appwrite/pull/9207) +* Update database in [#9202](https://github.com/appwrite/appwrite/pull/9202) +* Fix dead connections in [#9213](https://github.com/appwrite/appwrite/pull/9213) +* Revert "Fix dead connections" in [#9214](https://github.com/appwrite/appwrite/pull/9214) +* Add logs db init for consistency in [#9163](https://github.com/appwrite/appwrite/pull/9163) +* Split the collection definitions in [#9153](https://github.com/appwrite/appwrite/pull/9153) +* Log path with populated parameters in [#9220](https://github.com/appwrite/appwrite/pull/9220) +* Add missing scope on function template in [#9208](https://github.com/appwrite/appwrite/pull/9208) +* Add relatedCollection default in [#9225](https://github.com/appwrite/appwrite/pull/9225) +* fix: function usage in [#9235](https://github.com/appwrite/appwrite/pull/9235) +* feat: optimise events payloads in [#9232](https://github.com/appwrite/appwrite/pull/9232) +* Optimise webhook events in [#9168](https://github.com/appwrite/appwrite/pull/9168) +* fix: maintenance job missing type in [#9238](https://github.com/appwrite/appwrite/pull/9238) +* Update Fetch to 0.3.0 in [#9245](https://github.com/appwrite/appwrite/pull/9245) +* Fix maintenance job in [#9247](https://github.com/appwrite/appwrite/pull/9247) +* chore: add missing case for executions in [#9248](https://github.com/appwrite/appwrite/pull/9248) +* Add index dependency exception in [#9226](https://github.com/appwrite/appwrite/pull/9226) +* chore: fix benchmarking test when made from fork in [#9233](https://github.com/appwrite/appwrite/pull/9233) +* Update SDK Generator versions in [#9188](https://github.com/appwrite/appwrite/pull/9188) +* chore: skipped job instead of throwing error in [#9250](https://github.com/appwrite/appwrite/pull/9250) +* Implement new SDK Class on 1.6.x in [#9237](https://github.com/appwrite/appwrite/pull/9237) +* Delete collection before Appwrite's attributes in [#9256](https://github.com/appwrite/appwrite/pull/9256) +* Feat batch usage dump in [#9255](https://github.com/appwrite/appwrite/pull/9255) +* Fix cloud tests in [#9261](https://github.com/appwrite/appwrite/pull/9261) +* Usage: Databases reads writes in [#9260](https://github.com/appwrite/appwrite/pull/9260) +* Update: Latest sdk specs in [#9274](https://github.com/appwrite/appwrite/pull/9274) +* Revert "Feat batch usage dump" in [#9276](https://github.com/appwrite/appwrite/pull/9276) +* feat: add fast2SMS adapter in [#9263](https://github.com/appwrite/appwrite/pull/9263) +* Update Sdk Generator dependency in [#9280](https://github.com/appwrite/appwrite/pull/9280) +* Transformed at addition in [#9281](https://github.com/appwrite/appwrite/pull/9281) +* Docs: clarify update endpoints only work on draft messages in [#9236](https://github.com/appwrite/appwrite/pull/9236) +* Update sdk generator dependency in [#9282](https://github.com/appwrite/appwrite/pull/9282) +* Revert "Transformed at addition" in [#9284](https://github.com/appwrite/appwrite/pull/9284) +* replaced init for cloud link in [#9285](https://github.com/appwrite/appwrite/pull/9285) +* Add transformed at in [#9289](https://github.com/appwrite/appwrite/pull/9289) +* Make migrations use Dynamic keys for destination in [#9291](https://github.com/appwrite/appwrite/pull/9291) +* Make sessions limit tests assert eventually in [#9298](https://github.com/appwrite/appwrite/pull/9298) +* Chore update database in [#9306](https://github.com/appwrite/appwrite/pull/9306) +* feat: add AMQP queues in [#9287](https://github.com/appwrite/appwrite/pull/9287) +* fix(test): use assertEventually instead of while(true) in [#9308](https://github.com/appwrite/appwrite/pull/9308) +* fix(certificate worker): events are published without queue name in [#9309](https://github.com/appwrite/appwrite/pull/9309) +* chore: update utopia-php/queue to 0.8.1 in [#9311](https://github.com/appwrite/appwrite/pull/9311) +* chore: update utopia-php/queue to 0.8.2 in [#9312](https://github.com/appwrite/appwrite/pull/9312) +* fix(schedule-tasks): revert back to direct pool usage in [#9313](https://github.com/appwrite/appwrite/pull/9313) +* feat: custom app schemes in [#9262](https://github.com/appwrite/appwrite/pull/9262) +* Revert "feat: custom app schemes" in [#9319](https://github.com/appwrite/appwrite/pull/9319) +* Restore "feat: custom app schemes"" in [#9320](https://github.com/appwrite/appwrite/pull/9320) +* Revert "Restore "feat: custom app schemes""" in [#9323](https://github.com/appwrite/appwrite/pull/9323) +* chore: update dependencies in [#9330](https://github.com/appwrite/appwrite/pull/9330) +* Feat: logs DB in [#9272](https://github.com/appwrite/appwrite/pull/9272) +* Catch invalid index in [#9329](https://github.com/appwrite/appwrite/pull/9329) +* Fix: missing call for image transformations counting in [#9342](https://github.com/appwrite/appwrite/pull/9342) +* Fix drop abuse on shared table project delete in [#9346](https://github.com/appwrite/appwrite/pull/9346) +* Only run all table mode tests on db update in [#9338](https://github.com/appwrite/appwrite/pull/9338) +* Fix: missing periodic metric in [#9350](https://github.com/appwrite/appwrite/pull/9350) +* feat(builds): check if function is blocked before building in [#9332](https://github.com/appwrite/appwrite/pull/9332) +* feat: batch create audit logs in [#9347](https://github.com/appwrite/appwrite/pull/9347) +* Chore: Update migrations in [#9355](https://github.com/appwrite/appwrite/pull/9355) +* Fix: metric time was not being written to DB in [#9354](https://github.com/appwrite/appwrite/pull/9354) +* Fix patch index validation in [#9356](https://github.com/appwrite/appwrite/pull/9356) +* Fix image trnasformation metrics in [#9370](https://github.com/appwrite/appwrite/pull/9370) +* Use batch delete in worker in [#9375](https://github.com/appwrite/appwrite/pull/9375) +* Fix Model Platform is missing response key: store in [#9361](https://github.com/appwrite/appwrite/pull/9361) +* Feat key segmented usage in [#9336](https://github.com/appwrite/appwrite/pull/9336) +* Feat messaging metrics in [#9353](https://github.com/appwrite/appwrite/pull/9353) +* Fix removed audits for shared v2 in [#9388](https://github.com/appwrite/appwrite/pull/9388) +* chore: bump utopia-php/image to 0.8.0 in [#9390](https://github.com/appwrite/appwrite/pull/9390) +* Fix outdated CLI commands in documentation in [#9122](https://github.com/appwrite/appwrite/pull/9122) +* disable logs display in [#9398](https://github.com/appwrite/appwrite/pull/9398) +* Log batches per project in [#9403](https://github.com/appwrite/appwrite/pull/9403) +* Batch per project in [#9410](https://github.com/appwrite/appwrite/pull/9410) +* Fix: stats resources only queue projects accessed in last 3 hours in [#9411](https://github.com/appwrite/appwrite/pull/9411) +* Track options requests in [#9397](https://github.com/appwrite/appwrite/pull/9397) +* chore: bump docker-base in [#9406](https://github.com/appwrite/appwrite/pull/9406) +* refactor: migrate Realtime::send calls to queueForRealtime in [#9325](https://github.com/appwrite/appwrite/pull/9325) +* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [#9424](https://github.com/appwrite/appwrite/pull/9424) +* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [#9339](https://github.com/appwrite/appwrite/pull/9339) +* Fix: disable dual writing in [#9429](https://github.com/appwrite/appwrite/pull/9429) +* Disable transformedAt update for console users in [#9425](https://github.com/appwrite/appwrite/pull/9425) +* chore: add image transformation stats to usage endpoint in [#9393](https://github.com/appwrite/appwrite/pull/9393) +* chore: added timeout to deployment builds in tests in [#9426](https://github.com/appwrite/appwrite/pull/9426) +* fix: model for image transformations in usage project in [#9442](https://github.com/appwrite/appwrite/pull/9442) +* Feat: calculate database storage in stats-resources in [#9443](https://github.com/appwrite/appwrite/pull/9443) +* Activities batch writes in [#9438](https://github.com/appwrite/appwrite/pull/9438) +* chore: bump cache 0.12.x in [#9412](https://github.com/appwrite/appwrite/pull/9412) +* chore: queue console project for maintenance delete in [#9479](https://github.com/appwrite/appwrite/pull/9479) +* chore: added logsdb for deletes worker in [#9462](https://github.com/appwrite/appwrite/pull/9462) +* Feat: calculate and log time taken for each project in [#9491](https://github.com/appwrite/appwrite/pull/9491) +* chore: update initializing dbForLogs in [#9494](https://github.com/appwrite/appwrite/pull/9494) +* Feat bulk audit delete in [#9487](https://github.com/appwrite/appwrite/pull/9487) +* Prepare 1.6.2 release in [#9499](https://github.com/appwrite/appwrite/pull/9499) +* Regenerate specs in [#9497](https://github.com/appwrite/appwrite/pull/9497) +* Regenerate examples in [#9498](https://github.com/appwrite/appwrite/pull/9498) +* chore: bump sdk in [#9414](https://github.com/appwrite/appwrite/pull/9414) +* update queue to 0.9.* in [#9505](https://github.com/appwrite/appwrite/pull/9505) +* Feat improve delete queries in [#9507](https://github.com/appwrite/appwrite/pull/9507) +* Feat: Add rule attributes in [#9508](https://github.com/appwrite/appwrite/pull/9508) +* Sync main into 1.6.x in [#9496](https://github.com/appwrite/appwrite/pull/9496) +* Bump console to version 5.2.53 in [#9495](https://github.com/appwrite/appwrite/pull/9495) +* Prepare 1.6.1 release in [#9294](https://github.com/appwrite/appwrite/pull/9294) +* Improve delete ordering in [#9512](https://github.com/appwrite/appwrite/pull/9512) +* Cleanups in [#9511](https://github.com/appwrite/appwrite/pull/9511) +* Feat dynamic regions in [#9408](https://github.com/appwrite/appwrite/pull/9408) +* Feat env vars to system lib in [#9515](https://github.com/appwrite/appwrite/pull/9515) +* Feat: domains count in [#9514](https://github.com/appwrite/appwrite/pull/9514) +* Migration read from db in [#9529](https://github.com/appwrite/appwrite/pull/9529) +* feat: add pool telemetry in [#9530](https://github.com/appwrite/appwrite/pull/9530) +* Disable PDO persistence since we manage our own pool in [#9526](https://github.com/appwrite/appwrite/pull/9526) +* chore: set min operations to 1 for reads and writes in [#9536](https://github.com/appwrite/appwrite/pull/9536) +* Remove default region in [#9430](https://github.com/appwrite/appwrite/pull/9430) +* Use cursor pagination with bigger limit for maintenance project loop in [#9546](https://github.com/appwrite/appwrite/pull/9546) +* chore: stop tests on failure in [#9525](https://github.com/appwrite/appwrite/pull/9525) +* chore: only update total count for privileged users in [#9554](https://github.com/appwrite/appwrite/pull/9554) +* refactor: initialization of audit retention in [#9563](https://github.com/appwrite/appwrite/pull/9563) +* Delete worker queries fixes in [#9523](https://github.com/appwrite/appwrite/pull/9523) +* Bump database 0.62.x in [#9568](https://github.com/appwrite/appwrite/pull/9568) +* Fix: schedules region filtering in [#9577](https://github.com/appwrite/appwrite/pull/9577) +* Deletes worker fix selects for pagination in [#9578](https://github.com/appwrite/appwrite/pull/9578) +* Add $permissions for delete documents selects in [#9579](https://github.com/appwrite/appwrite/pull/9579) +* chore(audits): return queue pre-fetch results in [#9533](https://github.com/appwrite/appwrite/pull/9533) +* Revert "chore(audits): return queue pre-fetch results" in [#9586](https://github.com/appwrite/appwrite/pull/9586) +* Feat multi tenant insert in [#9573](https://github.com/appwrite/appwrite/pull/9573) +* Add order by for cursor in [#9588](https://github.com/appwrite/appwrite/pull/9588) +* Feat update fetch in [#9592](https://github.com/appwrite/appwrite/pull/9592) +* Fix tenant casting in [#9598](https://github.com/appwrite/appwrite/pull/9598) +* Feat update ws in [#9602](https://github.com/appwrite/appwrite/pull/9602) +* Update database in [#9603](https://github.com/appwrite/appwrite/pull/9603) +* Fix: image transformation cache in [#9608](https://github.com/appwrite/appwrite/pull/9608) +* Remove audit payload in [#9610](https://github.com/appwrite/appwrite/pull/9610) +* Sample rate from DSN in [#9559](https://github.com/appwrite/appwrite/pull/9559) +* Restrict role change for sole org owner in [#9615](https://github.com/appwrite/appwrite/pull/9615) +* chore: update php image to 0.8.1 in [#9616](https://github.com/appwrite/appwrite/pull/9616) +* feat: refactor executor setup in [#9420](https://github.com/appwrite/appwrite/pull/9420) +* chore: update gitpod.yml config in [#9561](https://github.com/appwrite/appwrite/pull/9561) +* chore: update dependencies in [#9625](https://github.com/appwrite/appwrite/pull/9625) +* Update migrations lib in [#9628](https://github.com/appwrite/appwrite/pull/9628) +* feat: cache telemetry in [#9624](https://github.com/appwrite/appwrite/pull/9624) +* Bump console to version 5.2.56 in [#9631](https://github.com/appwrite/appwrite/pull/9631) +* Multi region support in [#8667](https://github.com/appwrite/appwrite/pull/8667) +* Revert "Multi region support" in [#9632](https://github.com/appwrite/appwrite/pull/9632) +* Revert "Revert "Multi region support"" in [#9636](https://github.com/appwrite/appwrite/pull/9636) +* Fix tasks in [#9644](https://github.com/appwrite/appwrite/pull/9644) +* chore: updated the migration version to 8.6 in [#9646](https://github.com/appwrite/appwrite/pull/9646) +* Fix: merge the working of StatsUsage and StatsUsageDump in [#9585](https://github.com/appwrite/appwrite/pull/9585) +* Update database in [#9643](https://github.com/appwrite/appwrite/pull/9643) +* chore: fix error logging for CLI tasks in [#9651](https://github.com/appwrite/appwrite/pull/9651) +* fix: usage test assertion in [#9653](https://github.com/appwrite/appwrite/pull/9653) +* Fix keys in [#9656](https://github.com/appwrite/appwrite/pull/9656) +* Feat: multi tenant dual writing in [#9583](https://github.com/appwrite/appwrite/pull/9583) +* Fix/throwing 400 for null order attributes in [#9657](https://github.com/appwrite/appwrite/pull/9657) +* feat: sdk group attribute in [#9596](https://github.com/appwrite/appwrite/pull/9596) +* Add configurable function and build size in [#9648](https://github.com/appwrite/appwrite/pull/9648) +* feat: update API endpoint in the code examples in [#8933](https://github.com/appwrite/appwrite/pull/8933) +* chore: abstract token secret hiding to response model in [#9574](https://github.com/appwrite/appwrite/pull/9574) +* chore: update sdks in [#9655](https://github.com/appwrite/appwrite/pull/9655) +* feat: allow non-critical events to ignore exceptions when enqueuing the event in [#9680](https://github.com/appwrite/appwrite/pull/9680) +* Revert "Add configurable function and build size" in [#9681](https://github.com/appwrite/appwrite/pull/9681) +* core: introduce endpoint.docs in specs in [#9685](https://github.com/appwrite/appwrite/pull/9685) +* fix: remove content-type header from get request specs in [#9666](https://github.com/appwrite/appwrite/pull/9666) +* chore: update flutter sdk in [#9691](https://github.com/appwrite/appwrite/pull/9691) # Version 1.6.1 From 0e567e3e689d0d8b651fdf793c1cede476c43a5f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 21 Oct 2025 11:40:50 +0530 Subject: [PATCH 379/385] chore: update cli to 10.2.2 --- app/config/platforms.php | 2 +- docs/sdks/cli/CHANGELOG.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 2fcc296979..95429576fd 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -226,7 +226,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '10.2.1', + 'version' => '10.2.2', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index db3898dd00..ad355682a0 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 10.2.2 + +* Fix `logout` command showing duplicate sessions +* Fix `logout` command showing a blank email even when logged out +* Add syncing of `tablesDB` resource during `push tables` command + ## 10.2.1 * Add transaction support for Databases and TablesDB From 8154a8524aa64e5ee03df9c62a6153638fbcbb47 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 21 Oct 2025 12:51:10 +0530 Subject: [PATCH 380/385] update sdk gen --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 0dfe37ce9b..c4a8b67561 100644 --- a/composer.lock +++ b/composer.lock @@ -5224,16 +5224,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.4", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49" + "reference": "0c4f514bf861f42555dae781f394fefeb27ff521" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49", - "reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0c4f514bf861f42555dae781f394fefeb27ff521", + "reference": "0c4f514bf861f42555dae781f394fefeb27ff521", "shasum": "" }, "require": { @@ -5269,9 +5269,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.4" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.5" }, - "time": "2025-10-13T09:20:49+00:00" + "time": "2025-10-21T04:59:59+00:00" }, { "name": "doctrine/annotations", From 803244ccf713bda31219e2fe2aaba7acb6d7ac87 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 21 Oct 2025 11:48:48 +0000 Subject: [PATCH 381/385] feat:add _APP_COMPUTE_BUILD_TIMEOUT to console variables --- app/controllers/api/console.php | 1 + src/Appwrite/Utopia/Response/Model/ConsoleVariables.php | 6 ++++++ tests/e2e/Services/Console/ConsoleConsoleClientTest.php | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index b0619df3b3..01db0af7e2 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -71,6 +71,7 @@ App::get('/v1/console/variables') '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), + '_APP_COMPUTE_BUILD_TIMEOUT' => System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), // Combine CAA domain with most common flags and tag (no parameters) '_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"', '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), diff --git a/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php b/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php index b81502f0a1..4451769384 100644 --- a/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php +++ b/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php @@ -22,6 +22,12 @@ class ConsoleVariables extends Model 'default' => '', 'example' => '127.0.0.1', ]) + ->addRule('_APP_COMPUTE_BUILD_TIMEOUT', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Maximum build timeout in seconds.', + 'default' => '', + 'example' => 900, + ]) ->addRule('_APP_DOMAIN_TARGET_AAAA', [ 'type' => self::TYPE_STRING, 'description' => 'AAAA target for your Appwrite custom domains.', diff --git a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php index 340cabc8c0..d94b64515a 100644 --- a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php +++ b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php @@ -24,9 +24,10 @@ class ConsoleConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(14, $response['body']); + $this->assertCount(15, $response['body']); $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CNAME']); $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_A']); + $this->assertIsInt($response['body']['_APP_COMPUTE_BUILD_TIMEOUT']); $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_AAAA']); $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CAA']); $this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']); From 4cb20b3fe313e15907f087694b08fbace2f20142 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:47:30 +0530 Subject: [PATCH 382/385] Update app/controllers/api/console.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/controllers/api/console.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index 01db0af7e2..e9e5bfa4b6 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -71,7 +71,7 @@ App::get('/v1/console/variables') '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), - '_APP_COMPUTE_BUILD_TIMEOUT' => System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), + '_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), // Combine CAA domain with most common flags and tag (no parameters) '_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"', '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), From d48ef373a7c2745cb86032cbb9def7a2383312bf Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:48:48 +0530 Subject: [PATCH 383/385] Fix environment variable assignment for build timeout --- app/controllers/api/console.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index e9e5bfa4b6..ec68f1050c 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -71,10 +71,10 @@ App::get('/v1/console/variables') '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), - '_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), // Combine CAA domain with most common flags and tag (no parameters) '_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"', '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), + '_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), '_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'), '_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'), '_APP_VCS_ENABLED' => $isVcsEnabled, From 9c30f32281d61323aba52f84bc086866884f86f6 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 22 Oct 2025 09:29:45 +0530 Subject: [PATCH 384/385] update domain lib to 0.9.1 --- composer.json | 2 +- composer.lock | 150 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 123 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index bb843fd771..de5f296cd3 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,7 @@ "utopia-php/config": "0.2.*", "utopia-php/database": "3.*", "utopia-php/detector": "0.1.*", - "utopia-php/domains": "0.8.*", + "utopia-php/domains": "0.9.*", "utopia-php/dns": "0.3.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index c4a8b67561..607d9c4466 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": "407c1717bfef580d733ff2bbb232ec8a", + "content-hash": "b48bd33d40081c107b826f78981e0a54", "packages": [ { "name": "adhocore/jwt", @@ -3790,6 +3790,54 @@ }, "time": "2020-10-24T09:49:09+00:00" }, + { + "name": "utopia-php/console", + "version": "0.0.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/console.git", + "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/console/zipball/f77104e4a888fa9cb3e08f32955ec09479ab7a92", + "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6", + "swoole/ide-helper": "4.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Console helpers for logging, prompting, and executing commands", + "keywords": [ + "cli", + "console", + "php", + "terminal", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/console/issues", + "source": "https://github.com/utopia-php/console/tree/0.0.1" + }, + "time": "2025-10-20T14:41:36+00:00" + }, { "name": "utopia-php/database", "version": "3.0.2", @@ -3951,21 +3999,22 @@ }, { "name": "utopia-php/domains", - "version": "0.8.4", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22" + "reference": "99b4ec95d5d6b7a5c990a66c56412212d9af37e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/316a76b0fcbc7f82e40be466c22c19acb243ee22", - "reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/99b4ec95d5d6b7a5c990a66c56412212d9af37e7", + "reference": "99b4ec95d5d6b7a5c990a66c56412212d9af37e7", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/framework": "0.33.*" + "utopia-php/cache": "0.13.*", + "utopia-php/validators": "0.0.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -4006,9 +4055,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.8.4" + "source": "https://github.com/utopia-php/domains/tree/0.9.1" }, - "time": "2025-10-17T11:31:56+00:00" + "time": "2025-10-21T14:52:27+00:00" }, { "name": "utopia-php/dsn", @@ -4346,16 +4395,16 @@ }, { "name": "utopia-php/migration", - "version": "1.2.2", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "e0b6687620dd67fe2b96eb4419e8243577b11c8f" + "reference": "b6985b235ab64f07a6b88569e20cf9b2df7d838c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/e0b6687620dd67fe2b96eb4419e8243577b11c8f", - "reference": "e0b6687620dd67fe2b96eb4419e8243577b11c8f", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/b6985b235ab64f07a6b88569e20cf9b2df7d838c", + "reference": "b6985b235ab64f07a6b88569e20cf9b2df7d838c", "shasum": "" }, "require": { @@ -4363,9 +4412,9 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", + "utopia-php/console": "0.0.*", "utopia-php/database": "3.*", "utopia-php/dsn": "0.2.*", - "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" }, "require-dev": { @@ -4373,7 +4422,6 @@ "laravel/pint": "1.*", "phpstan/phpstan": "1.*", "phpunit/phpunit": "11.*", - "utopia-php/cli": "0.16.*", "vlucas/phpdotenv": "5.*" }, "type": "library", @@ -4396,9 +4444,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.2.2" + "source": "https://github.com/utopia-php/migration/tree/1.3.1" }, - "time": "2025-10-20T10:12:11+00:00" + "time": "2025-10-21T08:13:54+00:00" }, { "name": "utopia-php/mongo", @@ -4999,6 +5047,52 @@ }, "time": "2025-03-17T11:57:52+00:00" }, + { + "name": "utopia-php/validators", + "version": "0.0.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/validators.git", + "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/894210695c5d35fa248fb65f7fe7237b6ff4fb0b", + "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-xdebug": "*", + "laravel/pint": "^1.2", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "^9.5.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A lightweight collection of reusable validators for Utopia projects", + "keywords": [ + "php", + "utopia", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/utopia-php/validators/issues", + "source": "https://github.com/utopia-php/validators/tree/0.0.2" + }, + "time": "2025-10-20T21:52:28+00:00" + }, { "name": "utopia-php/vcs", "version": "0.11.0", @@ -5224,16 +5318,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.5", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "0c4f514bf861f42555dae781f394fefeb27ff521" + "reference": "997e27a1224767a8da890454213d3123936b64bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0c4f514bf861f42555dae781f394fefeb27ff521", - "reference": "0c4f514bf861f42555dae781f394fefeb27ff521", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/997e27a1224767a8da890454213d3123936b64bc", + "reference": "997e27a1224767a8da890454213d3123936b64bc", "shasum": "" }, "require": { @@ -5269,9 +5363,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.5" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.6" }, - "time": "2025-10-21T04:59:59+00:00" + "time": "2025-10-21T08:49:37+00:00" }, { "name": "doctrine/annotations", @@ -5748,16 +5842,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -5800,9 +5894,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", From c2d4997810aebf7211aefff729c1a5f49b639284 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 22 Oct 2025 14:17:57 +0530 Subject: [PATCH 385/385] update apple to 13.3.0 --- app/config/platforms.php | 2 +- composer.lock | 12 ++++++------ docs/sdks/apple/CHANGELOG.md | 4 ++++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 95429576fd..3b675b174c 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -79,7 +79,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '13.2.2', + 'version' => '13.3.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 607d9c4466..62d92e566f 100644 --- a/composer.lock +++ b/composer.lock @@ -5318,16 +5318,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.6", + "version": "1.4.7", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "997e27a1224767a8da890454213d3123936b64bc" + "reference": "a61c8be551e10f4970bf46f75a54e4b0385c550d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/997e27a1224767a8da890454213d3123936b64bc", - "reference": "997e27a1224767a8da890454213d3123936b64bc", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a61c8be551e10f4970bf46f75a54e4b0385c550d", + "reference": "a61c8be551e10f4970bf46f75a54e4b0385c550d", "shasum": "" }, "require": { @@ -5363,9 +5363,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.6" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.7" }, - "time": "2025-10-21T08:49:37+00:00" + "time": "2025-10-22T06:03:44+00:00" }, { "name": "doctrine/annotations", diff --git a/docs/sdks/apple/CHANGELOG.md b/docs/sdks/apple/CHANGELOG.md index 762cd24b88..9ffa37cdf8 100644 --- a/docs/sdks/apple/CHANGELOG.md +++ b/docs/sdks/apple/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 13.3.0 + +* Add `onOpen`, `onClose` and `onError` callbacks to `Realtime` service + ## 13.2.2 * Fix issue: Missing AppwriteEnums dependency causing build failure