From aa36d944dbbd1b57c5c7eb637e36749896b15632 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 Mar 2025 22:00:37 +0100 Subject: [PATCH 001/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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/159] 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 4478edc211bbbf8540ee83472611c9badc0b7ff5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Sep 2025 04:25:21 +0000 Subject: [PATCH 037/159] 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 038/159] 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 039/159] 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 040/159] 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 041/159] 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 042/159] 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 043/159] 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 55bebd92f3e90f52c04359627899771640ede713 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 06:27:58 +0000 Subject: [PATCH 044/159] 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 045/159] 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 046/159] 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 047/159] 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 048/159] 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 049/159] 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 72f5793928fcc5d7c28a6ec6d6f399c4df51d2b0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 11 Sep 2025 09:18:23 +0000 Subject: [PATCH 050/159] 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 c903caabcc4553d1dd3ff7f5dbb1369fce4b99a3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 14 Sep 2025 04:50:13 +0000 Subject: [PATCH 051/159] 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 052/159] 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 053/159] 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 054/159] 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 055/159] 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 056/159] 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 057/159] 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 058/159] 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 059/159] 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 060/159] 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 061/159] 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 062/159] 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 063/159] 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 064/159] 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 065/159] 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 679de3574f23cbd091405f5c99150332bac72b89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:11:33 +0000 Subject: [PATCH 066/159] Initial plan From fea3544d4bd7e89f4e3fd5dddccbc662a6ad5c43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:18:07 +0000 Subject: [PATCH 067/159] Implement dynamic specification defaults for Functions and Sites Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com> --- .../Platform/Modules/Compute/Base.php | 44 +++++++++++++++++++ .../Functions/Http/Functions/Create.php | 2 +- .../Functions/Http/Functions/Update.php | 2 +- .../Modules/Sites/Http/Sites/Create.php | 2 +- .../Modules/Sites/Http/Sites/Update.php | 2 +- 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 92805fbaf8..7115747fb8 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Compute; use Appwrite\Event\Build; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; +use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -19,6 +20,49 @@ use Utopia\VCS\Exception\RepositoryNotFound; class Base extends Action { + /** + * Get default specification based on plan and available specifications. + * + * @param array $plan The billing plan configuration + * @return string The appropriate default specification + */ + protected function getDefaultSpecification(array $plan): string + { + $specifications = Config::getParam('specifications', []); + + if (empty($specifications)) { + return APP_COMPUTE_SPECIFICATION_DEFAULT; + } + + // If there's a plan with runtime specifications, use the highest one from the plan + if (!empty($plan) && array_key_exists('runtimeSpecifications', $plan) && !empty($plan['runtimeSpecifications'])) { + $planSpecifications = $plan['runtimeSpecifications']; + // Find the highest specification in the plan + foreach (array_reverse(array_keys($specifications)) as $specKey) { + if (in_array($specKey, $planSpecifications)) { + return $specKey; + } + } + } + + // If no plan or plan-based specification, use the highest available specification + $maxCpus = (float) System::getEnv('_APP_COMPUTE_CPUS', 0); + $maxMemory = (int) System::getEnv('_APP_COMPUTE_MEMORY', 0); + + $highestSpec = APP_COMPUTE_SPECIFICATION_DEFAULT; + foreach (array_reverse(array_keys($specifications)) as $specKey) { + $spec = $specifications[$specKey]; + $withinCpuLimit = empty($maxCpus) || $spec['cpus'] <= $maxCpus; + $withinMemoryLimit = empty($maxMemory) || $spec['memory'] <= $maxMemory; + + if ($withinCpuLimit && $withinMemoryLimit) { + $highestSpec = $specKey; + break; + } + } + + return $highestSpec; + } public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 21a74f9a81..8e3e0c3772 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -92,7 +92,7 @@ class Create extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index aaff953af0..318c2a2032 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -89,7 +89,7 @@ class Update extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index 9be95441cb..a1633b8eba 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -78,7 +78,7 @@ class Create extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 80354d5067..72ec04a2a5 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -82,7 +82,7 @@ class Update extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), From 62014fda26633c1c77c4129ccf8f28ade1d85b93 Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:29:44 +0000 Subject: [PATCH 068/159] fix: implement dynamic specification defaults for Functions and Sites Prior to this, self-hosted instances would defualt to the lowest specification. Now, if there is no plan (ie. on self-hosted), the highest specification is used. If there is a plan, the lowest specification available in the plan is used. --- .../Platform/Modules/Compute/Base.php | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 7115747fb8..a538eb1497 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Compute; use Appwrite\Event\Build; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; +use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -29,40 +30,28 @@ class Base extends Action protected function getDefaultSpecification(array $plan): string { $specifications = Config::getParam('specifications', []); - + if (empty($specifications)) { return APP_COMPUTE_SPECIFICATION_DEFAULT; } - // If there's a plan with runtime specifications, use the highest one from the plan - if (!empty($plan) && array_key_exists('runtimeSpecifications', $plan) && !empty($plan['runtimeSpecifications'])) { - $planSpecifications = $plan['runtimeSpecifications']; - // Find the highest specification in the plan - foreach (array_reverse(array_keys($specifications)) as $specKey) { - if (in_array($specKey, $planSpecifications)) { - return $specKey; - } - } + $specificationValidator = new SpecificationValidator( + $plan, + $specifications, + System::getEnv('_APP_COMPUTE_CPUS', 0), + System::getEnv('_APP_COMPUTE_MEMORY', 0) + ); + $allowedSpecifications = $specificationValidator->getAllowedSpecifications(); + + // If there is no plan use the highest specification + if (empty($plan)) { + return end($allowedSpecifications) ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } - // If no plan or plan-based specification, use the highest available specification - $maxCpus = (float) System::getEnv('_APP_COMPUTE_CPUS', 0); - $maxMemory = (int) System::getEnv('_APP_COMPUTE_MEMORY', 0); - - $highestSpec = APP_COMPUTE_SPECIFICATION_DEFAULT; - foreach (array_reverse(array_keys($specifications)) as $specKey) { - $spec = $specifications[$specKey]; - $withinCpuLimit = empty($maxCpus) || $spec['cpus'] <= $maxCpus; - $withinMemoryLimit = empty($maxMemory) || $spec['memory'] <= $maxMemory; - - if ($withinCpuLimit && $withinMemoryLimit) { - $highestSpec = $specKey; - break; - } - } - - return $highestSpec; + // Otherwise, use the lowest specification available in the plan + return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } + public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); From 536cff9cc65a8836d0b5821c1bec231b44913845 Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:30:18 +0000 Subject: [PATCH 069/159] chore: add specification validator test --- .../Compute/Validator/SpecificationTest.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php diff --git a/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php b/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php new file mode 100644 index 0000000000..0505494e17 --- /dev/null +++ b/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php @@ -0,0 +1,75 @@ +specifications = Config::getParam('specifications', []); + } + + public function testGetAllowedSpecificationsNoLimits(): void + { + $validator = new Specification( + plan: [], + specifications: $this->specifications, + maxCpus: 0, + maxMemory: 0 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(count($this->specifications), $allowed); + $this->assertEquals( + $this->specifications[array_key_last($this->specifications)]['slug'], + $allowed[array_key_last($allowed)] + ); + } + + public function testGetAllowedSpecificationsWithMaxCpusAndMemory(): void + { + $validator = new Specification( + plan: [], + specifications: $this->specifications, + maxCpus: 2, + maxMemory: 2048 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(4, $allowed); + $this->assertEquals( + SpecificationConstants::S_2VCPU_2GB, + $allowed[array_key_last($allowed)] + ); + } + + public function testGetAllowedSpecificationsWithPlanLimits(): void + { + $plan = [ + 'runtimeSpecifications' => [ + SpecificationConstants::S_05VCPU_512MB, + SpecificationConstants::S_1VCPU_512MB + ] + ]; + $validator = new Specification( + plan: $plan, + specifications: $this->specifications, + maxCpus: 0, + maxMemory: 0 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(2, $allowed); + $this->assertContains(SpecificationConstants::S_05VCPU_512MB, $allowed); + $this->assertContains(SpecificationConstants::S_1VCPU_512MB, $allowed); + } +} From 1157d6fd100981f86fdb3a1ccdb84e7a2d3d834f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 16 Sep 2025 04:27:00 +0000 Subject: [PATCH 070/159] 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 071/159] 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 072/159] 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 74f181d7a834c4874f231c4c5dd727ae25ef1c8a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 17 Sep 2025 10:18:49 +0000 Subject: [PATCH 073/159] 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 074/159] 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 075/159] 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 5d5c1d8f43286c5baec29183ed77f2583c5d9bcb Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Mon, 22 Sep 2025 20:29:25 +0530 Subject: [PATCH 076/159] Fix Author URL in template deployments --- app/init/constants.php | 1 + src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index 28cf8a4052..6d723e9182 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_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite'; // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9547a752ef..c0d55aa7fb 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -467,11 +467,10 @@ class Builds extends Action } $providerCommitHash = \trim($stdout); - $authorUrl = "https://github.com/$cloneOwner"; $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); - $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); - $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); + $deployment->setAttribute('providerCommitAuthorUrl', APP_VCS_GITHUB_URL); + $deployment->setAttribute('providerCommitAuthor', APP_VCS_GITHUB_USERNAME); $deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function"); $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); From 432faaf2097640417db473538fa93ce66783a7c5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 9 Oct 2025 08:24:39 +0545 Subject: [PATCH 077/159] 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 0877aa2964f88f0560cb023e3c295842fed32a10 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 9 Oct 2025 21:04:50 +1300 Subject: [PATCH 078/159] 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 079/159] 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 080/159] 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 081/159] 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 082/159] 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 083/159] 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 084/159] 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 085/159] 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 086/159] 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 087/159] 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 088/159] 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 089/159] 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 090/159] 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 091/159] 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 092/159] 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 093/159] 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 094/159] 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 095/159] 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 096/159] 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 097/159] 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 098/159] 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 099/159] 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 100/159] 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 101/159] 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 102/159] 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 103/159] 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 104/159] 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 105/159] 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 106/159] 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 107/159] 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 108/159] 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 109/159] 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 110/159] 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 111/159] 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 112/159] 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 113/159] 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 114/159] 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 115/159] 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 116/159] 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 117/159] 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 118/159] 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 119/159] 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 120/159] 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 121/159] 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 122/159] 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 123/159] 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 124/159] 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 125/159] 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 126/159] 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 127/159] 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 128/159] 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 129/159] 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 130/159] 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 131/159] 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 132/159] 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 133/159] 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 134/159] 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 135/159] 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 136/159] 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 137/159] 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 138/159] 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 139/159] 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 140/159] 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 141/159] 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 142/159] 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 143/159] 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 144/159] 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 145/159] 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 146/159] 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 147/159] 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 148/159] 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 149/159] 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 150/159] 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 151/159] 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 152/159] 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 153/159] 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 154/159] 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 155/159] 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 156/159] 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 157/159] 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 158/159] 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 From 18371eb5116619c3cee3faf3b4388358160fe0fb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:00:26 +0100 Subject: [PATCH 159/159] fix: stats usage memory leak --- src/Appwrite/Platform/Workers/StatsUsage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 32cdb02dea..018c192647 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -435,7 +435,6 @@ class StatsUsage extends Action return $cmp; } - unset($this->projects[$sequence]); // Period ASC $cmp = strcmp($a['period'], $b['period']); if ($cmp !== 0) {