Refactor authorization handling across multiple modules to use the ne…#10682
Refactor authorization handling across multiple modules to use the ne…#10682
Conversation
…w getAuthorization() method. Update CHANGES.md to reflect the change in usage worker authorization. Adjust composer.json to specify the correct version for utopia-php/database. Update plugin-api-version in composer.lock.
📝 WalkthroughWalkthroughThe PR introduces a dependency-injected Authorization resource and replaces global/static Authorization usage with an injected per-request/per-context Authorization instance across the codebase. Authorization is registered as a public resource and threaded into resource factories (dbForPlatform, getProjectDB, getLogsDB), CLI/worker/server resources, HTTP routes, platform modules, tasks, and many constructors. Database, Request, and Response types receive/set Authorization via setAuthorization. Validator usage is updated in many places to Utopia\Database\Validator\Authorization\Input. composer.json updates utopia-php/database from "3." to "4.". Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Security Scan Results for PRDocker Image Scan Results
Source Code Scan Results🎉 No vulnerabilities found! |
…th-single-instance # Conflicts: # composer.lock
✨ Benchmark results
⚡ Benchmark Comparison
|
…orker modules. Adjust Docker configuration to include database source path. Modify error handling in general controller and enhance permission setting in document creation. Fix privileged user role retrieval in function execution. Ensure consistent authorization usage across various resources.
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (27)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
498-504: Critical syntax errors: Fix malformed parentheses.Lines 498 and 502 contain syntax errors with double opening parentheses that will prevent compilation:
- Line 498:
getAttribute(('type')should begetAttribute('type')- Line 502:
getAttribute(('filter')should begetAttribute('filter')Apply this diff to fix the syntax errors:
- if ($attribute->getAttribute(('type') !== $type)) { + if ($attribute->getAttribute('type') !== $type)) { throw new Exception($this->getTypeInvalidException()); } - if ($attribute->getAttribute('type') === Database::VAR_STRING && $attribute->getAttribute(('filter') !== $filter)) { + if ($attribute->getAttribute('type') === Database::VAR_STRING && $attribute->getAttribute('filter') !== $filter)) { throw new Exception($this->getTypeInvalidException()); }src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)
84-91: Add a guard for missing/empty schedule before update.If the function has no scheduleId or the schedule doc is empty, updating it will fail. Guard it to make deletion robust.
- $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('active', false); - $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + $scheduleId = $function->getAttribute('scheduleId'); + if (!empty($scheduleId)) { + $schedule = $dbForPlatform->getDocument('schedules', $scheduleId); + if (!$schedule->isEmpty()) { + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('active', false); + $dbForPlatform->getAuthorization()->skip( + fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule) + ); + } + }src/Appwrite/Platform/Modules/Compute/Base.php (3)
207-227: Fix authorization instance mismatch.The authorization skip is called on
$dbForProjectbut the operation is performed on$dbForPlatform. The authorization instance should match the database being operated on.Apply this diff:
- $dbForProject->getAuthorization()->skip( + $dbForPlatform->getAuthorization()->skip( fn () => $dbForPlatform->createDocument('rules', new Document([
233-253: Fix authorization instance mismatch.Same issue: authorization skip is called on
$dbForProjectbut the operation is performed on$dbForPlatform.Apply this diff:
- $dbForProject->getAuthorization()->skip( + $dbForPlatform->getAuthorization()->skip( fn () => $dbForPlatform->createDocument('rules', new Document([
270-290: Fix authorization instance mismatch.Same issue: authorization skip is called on
$dbForProjectbut the operation is performed on$dbForPlatform.Apply this diff:
- $dbForProject->getAuthorization()->skip( + $dbForPlatform->getAuthorization()->skip( fn () => $dbForPlatform->createDocument('rules', new Document([src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php (1)
239-250: Inconsistent authorization context for platform database operation.This code uses
$dbForProject->getAuthorization()->skip(...)but then creates a document in$dbForPlatform. The authorization context should match the database being operated on.Apply this diff to use the correct authorization context:
-$schedule = $dbForProject->getAuthorization()->skip( +$schedule = $dbForPlatform->getAuthorization()->skip( fn () => $dbForPlatform->createDocument('schedules', new Document([src/Appwrite/Utopia/Response.php (1)
811-819: Potential fatal: $authorization may be uninitialized.output() reads $this->authorization->getRoles(); if setAuthorization() wasn’t called for a code path, PHP will throw “Typed property must not be accessed before initialization.” Add a safe default or nullability guard.
Apply this minimal defensive fix:
- private Authorization $authorization; + private ?Authorization $authorization = null; @@ - if ($rule['sensitive']) { - $roles = $this->authorization->getRoles(); + if ($rule['sensitive']) { + $roles = $this->authorization ? $this->authorization->getRoles() : []; $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles);This preserves behavior when authorization is injected and avoids fatals otherwise (defaulting to hiding sensitive fields).
Also applies to: 981-986
app/controllers/api/migrations.php (1)
346-351: Fix cross‑DB skip: bypass must match the DB you queryYou skip authorization on the platform DB but execute reads on the project DB inside the closure. The bypass won’t apply to $dbForProject, which can cause false 401/404 on private buckets/files. Use the matching DB’s authorization per branch.
Apply:
- $bucket = $dbForPlatform->getAuthorization()->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) { - if ($internalFile) { - return $dbForPlatform->getDocument('buckets', 'default'); - } - return $dbForProject->getDocument('buckets', $bucketId); - }); + $bucket = $internalFile + ? $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('buckets', 'default')) + : $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));- $file = $dbForPlatform->getAuthorization()->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); + $file = $internalFile + ? $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId)) + : $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));Also applies to: 357-357
app/controllers/api/vcs.php (2)
332-353: Use platform DB’s authorization when writing platform ‘rules’The closure creates documents on $dbForPlatform but you call skip() on $dbForProject. The bypass won’t apply to platform writes.
Apply:
- $dbForProject->getAuthorization()->skip( + $dbForPlatform->getAuthorization()->skip( fn () => $dbForPlatform->createDocument('rules', new Document([Repeat the same replacement for the branch preview and commit preview rule creations in this block.
Also applies to: 365-385, 397-416
1667-1667: Fix createGitDeployments call: wrong arity and orderingThe helper expects 19 parameters. You pass 11 in the wrong order; this will fatal.
Apply:
- $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request); + $providerBranchUrl = "https://github.com/{$owner}/{$repositoryName}/tree/{$providerBranch}"; + $providerRepositoryUrl = "https://github.com/{$owner}/{$repositoryName}"; + $createGitDeployments( + $github, + $providerInstallationId, + $repositories, + $providerBranch, + $providerBranchUrl, + $repositoryName, + $providerRepositoryUrl, + $owner, + $providerCommitHash, + '', // providerCommitAuthor + '', // providerCommitAuthorUrl + '', // providerCommitMessage + '', // providerCommitUrl + $providerPullRequestId, + true, + $dbForPlatform, + $queueForBuilds, + $getProjectDB, + $request + );app/controllers/api/graphql.php (1)
23-23: Wrong Authorization type imported; will crash at runtimeValidator\Authorization doesn’t expose getRoles() and won’t match the injected resource. Import the runtime Authorization class instead.
Apply:
- use Utopia\Database\Validator\Authorization; + use Utopia\Database\Authorization;Also applies to: 31-37
app/controllers/api/account.php (2)
1743-1747: Typo: TYPE::EMAIL should be Type::EMAILThis will fatal (class 'TYPE' not found).
Apply:
- 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks + 'factors' => [Type::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
1000-1120: Fix spacing in-> skip(to->skip(at line 1076One occurrence found. Remove space after
->beforeskip:$dbForProject->getAuthorization()->skip(fn () => $dbForProject->createDocument('users', $user));app/controllers/api/messaging.php (5)
2142-2164: APNS: credential key mismatch breaks enablementYou set credentials['bundle'] but the enable check expects 'bundleId'. This prevents enabling APNS providers.
- if (!empty($bundleId)) { - $credentials['bundle'] = $bundleId; - } + if (!empty($bundleId)) { + $credentials['bundleId'] = $bundleId; + }
2316-2331: Topics listing cursor: assigning a Document as array index causes fatalgetDocument returns a Document; using $cursorDocument[0] will error. Assign the Document directly.
- $cursor->setValue($cursorDocument[0]); + $cursor->setValue($cursorDocument);
4356-4395: Push update: wrong exception and mime whitelist drift (mirror of create)Same fixes as createPush.
- $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); - if ($file->isEmpty()) { - throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); - } - - if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { + $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); + if ($file->isEmpty()) { + throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); + } + + if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg', 'image/bmp'])) { throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED); }
396-405: MSG91 create: enable-gating check always fails due to nonexistentoptions['from']; align with update endpointThe enable check at lines 396–405 validates
array_key_exists('from', $options), but$optionsis initialized empty and never populated in the MSG91 create function. This causes the enable guard to always fail. The MSG91 update endpoint correctly checks fortemplateIdin credentials; the create endpoint should do the same, sincetemplateIdis an accepted parameter (line 371) and stored in credentials (line 385).- if ( - $enabled === true - && \array_key_exists('senderId', $credentials) - && \array_key_exists('authKey', $credentials) - && \array_key_exists('from', $options) - ) { + if ( + $enabled === true + && \array_key_exists('senderId', $credentials) + && \array_key_exists('authKey', $credentials) + && \array_key_exists('templateId', $credentials) + ) { $enabled = true; } else { $enabled = false; }
3359-3398: Fix push create: undefined $currentScheduledAt, wrong exception, and mime whitelist drift
- File: app/controllers/api/messaging.php (approx. lines 3359–3398)
- if ($file->isEmpty()) => throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
- Allow BMP consistently: add 'image/bmp' to the mime whitelist or remove "bmp" from the param/docs if downstream push adapters do not support BMP.
- Replace $scheduleTime = $currentScheduledAt ?? $scheduledAt; with $scheduleTime = $scheduledAt; ($currentScheduledAt is undefined in the create action).
Verify BMP support in downstream push adapters; if unsupported, remove BMP from docs instead of changing the whitelist.
app/realtime.php (2)
457-466: Role recomputation should use Auth::getRoles(...)Authorization->getRoles($user) is inconsistent with the rest of the codebase and may not exist. Use Auth::getRoles($user, $authorization).
- $roles = $consoleDatabase->getAuthorization()->getRoles($user); + $roles = Auth::getRoles($user, $consoleDatabase->getAuthorization());
73-121: Fix getProjectDB signature to accept and apply Authorization parameterThe function is called with two arguments at line 641 (
getProjectDB($project, $database->getAuthorization())) but only accepts one. This causes the authorization context to be dropped. Additionally, the cached Database instance never gets updated with per-request authorization, unlike the implementations inapp/worker.phpandapp/init/resources.phpwhich applysetAuthorization()on each call.Update
app/realtime.phplines 73–121 to accept and apply the Authorization parameter:-if (!function_exists('getProjectDB')) { - function getProjectDB(Document $project): Database +if (!function_exists('getProjectDB')) { + function getProjectDB(Document $project, ?Authorization $authorization = null): Database { global $register; static $databases = []; if (isset($databases[$project->getSequence()])) { - return $databases[$project->getSequence()]; + $database = $databases[$project->getSequence()]; + if ($authorization) { + $database->setAuthorization($authorization); + } + return $database; }Also update the return statement to apply authorization before caching:
$database ->setMetadata('host', \gethostname()) ->setMetadata('project', $project->getId()); + if ($authorization) { + $database->setAuthorization($authorization); + } + return $databases[$project->getSequence()] = $database;src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (2)
125-130: Fix Datetime validator constant referenceWrong class name in constant reference will fatal.
- $validator = new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60); + $validator = new DatetimeValidator(requireDateInFuture: true, precision: DatetimeValidator::PRECISION_MINUTES, offset: 60);
135-140: Parse assoc JSON params: condition checks 'headers' string instead of variableThe guard always evaluates truthy and will json_decode non-strings. Use the variable.
- foreach ($assocParams as $assocParam) { - if (!empty('headers') && !is_array($$assocParam)) { - $$assocParam = \json_decode($$assocParam, true); - } - } + foreach ($assocParams as $assocParam) { + if (!empty($$assocParam) && !is_array($$assocParam)) { + $$assocParam = \json_decode($$assocParam, true); + } + }app/controllers/general.php (3)
922-993: Always re-enable authorization — wrap disable/reset in try/finally.If an exception occurs between disable() and reset(), authorization can remain disabled for the remainder of the request. Use a finally block.
- $dbForPlatform->getAuthorization()->disable(); - ... - $domains[$domain->get()] = true; - - $dbForPlatform->getAuthorization()->reset(); // ensure authorization is re-enabled + $dbForPlatform->getAuthorization()->disable(); + try { + ... + $domains[$domain->get()] = true; + } finally { + // ensure authorization is re-enabled even on exceptions + $dbForPlatform->getAuthorization()->reset(); + }
700-736: Guard against undefined $executionResponse after exceptions.If the try block throws before $executionResponse is set, later loops read it unconditionally. Initialize it and null‑coalesce headers in the loop.
@@ - /** Execute function */ - try { + /** Execute function */ + // Default to safe fallback in case execution fails early + $executionResponse = [ + 'statusCode' => 500, + 'headers' => [], + 'body' => '', + 'duration' => 0, + 'logs' => '', + 'errors' => '', + ]; + try { @@ - foreach ($executionResponse['headers'] as $name => $values) { + foreach (($executionResponse['headers'] ?? []) as $name => $values) {Also applies to: 711-731, 512-525
1527-1529: Fix path traversal check (operator precedence bug).The negation is applied to the substring, not the comparison. This condition never triggers, weakening the safety check.
- if (!\substr($absolute, 0, \strlen($base)) === $base) { + if (\substr($absolute, 0, \strlen($base)) !== $base) { throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, 'Invalid path'); }app/controllers/api/teams.php (1)
471-479: Fix roles whitelist: unused array_filter and stray semicolon allow reserved roles.array_filter result isn’t assigned; reserved roles may remain allowed. Also remove the no‑op semicolon.
Apply:
- if ($project->getId() === 'console') { - ; - $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]); - }); + if ($project->getId() === 'console') { + $roles = array_keys(Config::getParam('roles', [])); + $roles = array_values(array_filter($roles, function ($role) { + return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS], true); + })); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);app/cli.php (1)
176-203: getLogsDB cache bug: $database captured by value; caching won’t persist.Use by‑reference capture so subsequent invocations reuse the instance.
-CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) { +CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) { $database = null; - return function (?Document $project = null) use ($pools, $cache, $database, $authorization) { + return function (?Document $project = null) use ($pools, $cache, &$database, $authorization) {
🧹 Nitpick comments (31)
src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php (1)
80-82: Remove duplicate empty check.Lines 76–78 already verify that
$variableis not empty (among other conditions). This second empty check is unreachable and can be safely removed.Apply this diff:
- if ($variable === false || $variable->isEmpty()) { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } -src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php (1)
114-120: Verify if the nestedskip()call is necessary.The
updateDocumentoperation on line 119 is already wrapped in askip()call on line 114 that encompasses the entireforeachoperation. The innerskip()on line 119 appears redundant, as authorization should already be bypassed within the outer closure's scope.Please verify whether the nested skip is necessary or if the inner call can be removed.
If the inner skip is confirmed redundant, apply this diff:
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) { $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getSequence()); - $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), $rule)); + $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); }, $queries));docker-compose.yml (1)
92-92: Guard the vendor source mount behind a compose profile.Great for iterating on utopia-php/database locally, but it can mismatch composer’s installed version. Recommend enabling this only under a dedicated profile to avoid accidental use outside dev.
Example:
appwrite: volumes: @@ - - ./vendor/utopia-php/database/src:/usr/src/code/vendor/utopia-php/database/src + # Enable only when hacking on utopia-php/database + - ./vendor/utopia-php/database/src:/usr/src/code/vendor/utopia-php/database/src + # profiles: + # - hack-dbThen run with: docker compose --profile hack-db up.
Please confirm this file isn’t used in any CI or prod-like environment.
src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php (1)
106-112: Guard schedule update when schedule is absent.Updating the platform schedule without ensuring scheduleId exists and the doc is present can fail.
- $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('deploymentId'))); - $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + $scheduleId = $function->getAttribute('scheduleId'); + if (!empty($scheduleId)) { + $schedule = $dbForPlatform->getDocument('schedules', $scheduleId); + if (!$schedule->isEmpty()) { + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId'))); + $dbForPlatform->getAuthorization()->skip( + fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule) + ); + } + }Please confirm scheduleId is always set when variables change; if guaranteed, we can downgrade this to a no‑op.
src/Appwrite/Platform/Tasks/StatsResources.php (1)
63-66: Configure authorization once and use a single reference.Move disable() and setDefaultStatus(false) outside the loop (or guard with a one‑time flag) and consistently use $this->dbForPlatform inside the closure to avoid mixed references.
- Console::loop(function () use ($queue, $dbForPlatform) { - $dbForPlatform->getAuthorization()->disable(); - $dbForPlatform->getAuthorization()->setDefaultStatus(false); + // Configure auth once + $this->dbForPlatform->getAuthorization()->disable(); + $this->dbForPlatform->getAuthorization()->setDefaultStatus(false); + Console::loop(function () use ($queue) {src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Action.php (1)
24-26: Auth check looks good; consider consistent permission access.Optional: use getPermissionsByType(Database::PERMISSION_READ) for consistency with other modules.
- if (!$dbForProject->getAuthorization()->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()))) { + if (!$dbForProject->getAuthorization()->isValid( + new Input(Database::PERMISSION_READ, $bucket->getPermissionsByType(Database::PERMISSION_READ)) + )) {src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Create.php (1)
81-85: Return auth description on 401 and align permission accessor.Improve error context and consistency with sibling Action:
- $bucketPermission = $dbForProject->getAuthorization()->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate())); + $bucketPermission = $dbForProject->getAuthorization()->isValid( + new Input(Database::PERMISSION_UPDATE, $bucket->getPermissionsByType(Database::PERMISSION_UPDATE)) + ); @@ - $filePermission = $dbForProject->getAuthorization()->isValid(new Input(Database::PERMISSION_UPDATE, $file->getUpdate())); + $filePermission = $dbForProject->getAuthorization()->isValid( + new Input(Database::PERMISSION_UPDATE, $file->getPermissionsByType(Database::PERMISSION_UPDATE)) + ); @@ - if (!$bucketPermission && !$filePermission) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } + if (!$bucketPermission && !$filePermission) { + throw new Exception(Exception::USER_UNAUTHORIZED, $dbForProject->getAuthorization()->getDescription()); + } @@ - } elseif (!$bucketPermission) { - throw new Exception(Exception::USER_UNAUTHORIZED); + } elseif (!$bucketPermission) { + throw new Exception(Exception::USER_UNAUTHORIZED, $dbForProject->getAuthorization()->getDescription()); }Also applies to: 88-90
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (2)
145-150: Avoid unbounded scans; consider chunking.Query::limit(PHP_INT_MAX) and fetching database/collection metadata inside the main loop can be heavy on large transactions. Prefer paging logs (e.g., batched find with cursor/limit) and prefetching required metadata into a local cache keyed by internal IDs.
Also applies to: 286-293
233-261: Deduplicate repeated “set status=failed” updates.The same updateDocument('transactions', ..., ['status' => 'failed']) appears in multiple catch blocks. Extract a small helper/closure to reduce duplication and chances of drift.
tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
20-28: Cache the Authorization instance in testsReturn a stable instance to make cleanRoles()/addRole() deterministic across calls.
Apply:
- private $authorization; + private $authorization; @@ - public function getAuthorization(): Authorization - { - if (isset($this->authorization)) { - return $this->authorization; - } - return new Authorization(); - } + public function getAuthorization(): Authorization + { + if (!isset($this->authorization)) { + $this->authorization = new Authorization(); + } + return $this->authorization; + }Also applies to: 125-127, 147-149, 159-161, 235-237
tests/unit/Messaging/MessagingChannelsTest.php (1)
38-46: Return a stable Authorization instanceAvoid creating a new Authorization per call; cache it on first use.
- public function getAuthorization(): Authorization - { - if (isset($this->authorization)) { - return $this->authorization; - } - return new Authorization(); - } + public function getAuthorization(): Authorization + { + if (!isset($this->authorization)) { + $this->authorization = new Authorization(); + } + return $this->authorization; + }src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php (1)
76-78: Minor: avoid duplicate getRoles() callsStore roles once to compute both flags.
- $isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); + $roles = $dbForProject->getAuthorization()->getRoles(); + $isAPIKey = Auth::isAppUser($roles); + $isPrivilegedUser = Auth::isPrivilegedUser($roles);src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php (1)
100-104: DRY: cache the Authorization instance locally.Reduce repeated getAuthorization() calls and ease future changes.
- $database = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); + $auth = $dbForProject->getAuthorization(); + $database = $auth->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $isAPIKey = Auth::isAppUser($auth->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles());src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
78-82: Instance-based Authorization usage is consistent.Role checks and Authorization::skip(...) replacements via $dbForProject->getAuthorization() look correct.
Cache $auth locally to avoid repeated getAuthorization() calls:
- $isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); - $database = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $auth = $dbForProject->getAuthorization(); + $isAPIKey = Auth::isAppUser($auth->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); + $database = $auth->skip(fn () => $dbForProject->getDocument('databases', $databaseId));Also applies to: 86-87, 114-115
app/init/database/filters.php (1)
347-357: Also skip auth when fetching targets to keep semantics consistent.You skip for subscribers, but not for the subsequent targets find. If callers rely on bypassing permissions for this derived lookup, wrap it too.
- if (\count($targetIds) > 0) { - return $database->skipValidation(fn () => $database->find('targets', [ - Query::equal('$sequence', $targetIds) - ])); - } + if (\count($targetIds) > 0) { + return $database->getAuthorization()->skip( + fn () => $database->skipValidation( + fn () => $database->find('targets', [ + Query::equal('$sequence', $targetIds) + ]) + ) + ); + }Please confirm whether the previous behavior also skipped auth for targets; adjust if necessary.
tests/unit/Auth/AuthTest.php (1)
32-34: tearDown relies on the cached instance.After fixing getAuthorization(), this remains correct. Optionally reset the property to null if you want a fresh instance per test.
public function tearDown(): void { $this->getAuthorization()->cleanRoles(); $this->getAuthorization()->addRole(Role::any()->toString()); + // Optionally: + // $this->authorization = null; }app/controllers/api/account.php (1)
5024-5052: Guard optional provider lookup; avoid empty-ID fetchOnly fetch provider when providerId is present; prevents unnecessary DB read and null internalId mismatches.
Apply:
- $provider = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $provider = null; + if (!empty($providerId)) { + $provider = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('providers', $providerId)); + } @@ - 'providerId' => !empty($providerId) ? $providerId : null, - 'providerInternalId' => !empty($providerId) ? $provider->getSequence() : null, + 'providerId' => !empty($providerId) ? $providerId : null, + 'providerInternalId' => (!empty($providerId) && $provider && !$provider->isEmpty()) ? $provider->getSequence() : null,src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (2)
79-79: Align permissions allowlist between validator and aggregationValidator allows WRITE, but aggregation only expands READ/UPDATE/DELETE. Make both consistent to avoid surprising behavior.
Option A: Allow WRITE in aggregation (and expand to the concrete permissions).
Option B (preferred here): Remove WRITE from the validator if not supported by this endpoint.Example (Option B):
- ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)Also applies to: 130-135
99-103: Minor: cache $auth to reduce repetitionStore $auth = $dbForProject->getAuthorization() once and reuse. Small readability win.
Also applies to: 136-154, 151-155, 248-255
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (2)
82-82: Align permissions allowlist with aggregationKeep validator and aggregation in sync to avoid accepting WRITE but never expanding it.
Apply same approach as suggested in Update.php (either add WRITE to aggregation or drop it from validator).
Also applies to: 126-127
107-116: Minor: cache $auth to reduce repetitionUse a local $auth = $dbForProject->getAuthorization() to simplify repeated calls.
Also applies to: 151-170, 202-205, 219-223, 255-258
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (1)
180-182: Avoid repeated role lookups.Micro-optimization/readability: fetch roles once.
- $isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); + $roles = $dbForProject->getAuthorization()->getRoles(); + $isAPIKey = Auth::isAppUser($roles); + $isPrivilegedUser = Auth::isPrivilegedUser($roles);app/controllers/shared/api.php (1)
470-476: Reuse a single $roles variable per request block.You already compute $roles at Line 472; pass it through and reuse to avoid multiple getRoles() calls.
Also applies to: 572-576, 589-592, 871-873
app/controllers/api/storage.php (1)
431-442: Minor: cache roles and reuse across checks.Within each handler, fetch roles once and reuse for isAppUser/isPrivilegedUser and error messages.
- $isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); + $roles = $dbForProject->getAuthorization()->getRoles(); + $isAPIKey = Auth::isAppUser($roles); + $isPrivilegedUser = Auth::isPrivilegedUser($roles); @@ - $roles = $dbForProject->getAuthorization()->getRoles(); + // reuse $roles from aboveAlso applies to: 463-479
app/controllers/api/teams.php (2)
604-606: Prefer Role helper over string literal for owner check.Avoid string typos; use API:
-$isOwner = $dbForProject->getAuthorization()->hasRole('team:' . $team->getId() . '/owner'); +$isOwner = $dbForProject->getAuthorization()->hasRole(Role::team($team->getId(), 'owner')->toString());
1232-1266: Duplicate addRole for the same user; remove one.Role is added at Line 1236 and again at Line 1264. Keep one to avoid redundant mutations/log noise.
- $dbForProject->getAuthorization()->addRole(Role::user($user->getId())->toString()); + $dbForProject->getAuthorization()->addRole(Role::user($user->getId())->toString()); ... - $dbForProject->getAuthorization()->addRole(Role::user($userId)->toString()); + // already added aboveapp/init/resources.php (3)
457-491: Duplicate setAuthorization in getProjectDB (new-instance path).$configure() already sets authorization; the earlier call is redundant.
- $adapter = new DatabasePool($pools->get($dsn->getHost())); - $database = new Database($adapter, $cache); - $database->setAuthorization($authorization); + $adapter = new DatabasePool($pools->get($dsn->getHost())); + $database = new Database($adapter, $cache); $databases[$dsn->getHost()] = $database; $configure($database);
229-240: Use the injected Authorization directly for clarity.Since the same Authorization instance is set on both DBs, calling setDefaultStatus on it directly is clearer.
- $dbForPlatform->getAuthorization()->setDefaultStatus(true); + $authorization = $dbForPlatform->getAuthorization(); + $authorization->setDefaultStatus(true);
496-523: Authorization not set on adapter in init/resources.php.Verified: workers set
AuthorizationonDatabasePooladapter (app/worker.php:152), while all fourDatabasePoolinstances in app/init/resources.php (lines 399, 428, 486, 505) set it only on theDatabaseobject. The patterns differ consistently. If adapter-level authorization is required for feature parity, align the init/resources.php instances accordingly.app/worker.php (1)
58-68: Optional: add standard timeouts/metadata to dbForPlatform (parity with API).Not required, but aligning with API defaults (timeouts, metadata) could aid observability and consistency.
app/cli.php (1)
150-154: Set authorization on the adapter in DatabasePool instantiations for consistency with worker.php.Verification confirms that
DatabasePool::setAuthorizationexists and is called inapp/worker.php(line 152), but not inapp/cli.phporapp/init/resources.php. All three locations instantiateDatabasePoolbut only worker.php calls$adapter->setAuthorization($authorization)before wrapping it in aDatabaseobject.The adapter propagates its authorization to underlying pooled adapters when delegating calls, so aligning cli.php (lines 76, 150, 185) and init/resources.php (lines 399, 428, 486, 505) with the worker pattern would ensure consistent behavior across entry points.
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php
Outdated
Show resolved
Hide resolved
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
Show resolved
Hide resolved
…base, reflecting new versioning and aliases. Refactor document validation in Create operation to utilize the new authorization method for better security handling.
…h-single-instance
…nto refactor-auth-single-instance
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/controllers/api/messaging.php (1)
2503-2509: Fix array indexing bug on cursor document.Line 2509 incorrectly uses array indexing
$cursorDocument[0]on a Document object. ThegetDocumentmethod returns a Document object, not an array. This pattern is inconsistent with similar code elsewhere in the file (lines 1104, 2915) and will cause runtime errors.Apply this diff to fix the issue:
- $cursor->setValue($cursorDocument[0]); + $cursor->setValue($cursorDocument);
♻️ Duplicate comments (1)
composer.json (1)
51-51: Remove or stabilize development branch dependencies before releasing.Both
utopia-php/auditandutopia-php/databaseare pinned to development branches (dev-feat-auth-single-instanceanddev-feat-authorization-instance). This is problematic for a production release:
- utopia-php/database (line 55): As noted in the previous review,
feat-authorization-instanceremains unmerged and diverged from main (31 commits ahead, 5 behind). Stable version 3.0.2 is already available.- utopia-php/audit (line 51): Same concern applies—dev branches introduce instability and obscure dependency management.
Before merging to a release branch (1.8.x), replace these with stable versions:
- "utopia-php/audit": "dev-feat-auth-single-instance as 1.1.0", + "utopia-php/audit": "^1.1.0",- "utopia-php/database": "dev-feat-authorization-instance as 3.0.2", + "utopia-php/database": "^3.0.2",Alternatively, merge both feature branches into their respective
mainbranches and publish stable releases before referencing them here.Also applies to: 55-55
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
.github/labeler.yml(1 hunks).github/workflows/auto-label-issue.yml(1 hunks)app/controllers/api/messaging.php(13 hunks)app/controllers/api/vcs.php(17 hunks)composer.json(1 hunks)docker-compose.yml(1 hunks)src/Appwrite/Platform/Modules/Compute/Base.php(3 hunks)src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php(2 hunks)src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php(1 hunks)src/Appwrite/Platform/Modules/Functions/Workers/Builds.php(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- src/Appwrite/Platform/Modules/Compute/Base.php
- src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
- app/controllers/api/vcs.php
- docker-compose.yml
🧰 Additional context used
🧬 Code graph analysis (2)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php (4)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/unit/Messaging/MessagingChannelsTest.php (1)
getAuthorization(40-46)tests/unit/Auth/AuthTest.php (1)
getAuthorization(18-24)
app/controllers/api/messaging.php (4)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/unit/Messaging/MessagingChannelsTest.php (1)
getAuthorization(40-46)tests/unit/Auth/AuthTest.php (1)
getAuthorization(18-24)
🪛 GitHub Actions: Linter
composer.json
[error] 1-1: Lock file is not up to date with composer.json and required package mismatch: utopia-php/detector (0.2.0) does not satisfy constraint "0.1.*". Run 'composer update' to resolve.
[error] 1-1: Commit contains merge/conflict edits to composer.json. Prefer using 'composer require' to modify dependencies rather than editing directly.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Webhooks, Shared V1)
- GitHub Check: E2E Shared Mode Service Test (Databases/TablesDB, Shared V2)
- GitHub Check: E2E Shared Mode Service Test (Projects, Shared V1)
- GitHub Check: E2E Shared Mode Service Test (Projects, Shared V2)
- GitHub Check: E2E Shared Mode Service Test (Databases/Legacy, Shared V2)
- GitHub Check: E2E Shared Mode Service Test (FunctionsSchedule, Shared V2)
- GitHub Check: E2E Shared Mode Service Test (GraphQL, Shared V1)
- GitHub Check: E2E Shared Mode Service Test (GraphQL, Shared V2)
- GitHub Check: E2E Service Test (Messaging)
- GitHub Check: E2E Service Test (Account)
- GitHub Check: E2E Service Test (Databases/Legacy)
- GitHub Check: E2E Service Test (Databases/TablesDB)
- GitHub Check: E2E Service Test (GraphQL)
- GitHub Check: E2E Shared Mode Service Test (Site Screenshots) (Shared V1)
🔇 Additional comments (12)
.github/workflows/auto-label-issue.yml (1)
1-21: Workflow configuration looks good.The setup is clean: proper permissions (minimal scope), correct trigger for automated issue labeling, and sensible action configuration to include both title and body in label matching.
.github/labeler.yml (1)
62-75: Overlapping keywords may cause issues to receive multiple labels.The keywords "domain", "ssl", and "certificate" appear in both "product / domains" (line 63) and "product / sites" (line 75). If an issue mentions these terms, it will likely receive both labels.
Verify whether this overlap is intentional (e.g., for issues spanning both domains and sites) or if the patterns should be made more specific to avoid double-labeling.
src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php (1)
284-284: LGTM! Clean refactoring to instance-based authorization.The change correctly replaces the static
Authorization::skip()call with$dbForPlatform->getAuthorization()->skip(), aligning with the PR's architectural shift to per-instance authorization. The logic and behavior remain unchanged.app/controllers/api/messaging.php (9)
38-38: LGTM! Import updated correctly for the new authorization pattern.The import change from
Authorizationvalidator toAuthorization\Inputvalidator correctly supports the refactored authorization handling throughout this file.
1098-1098: LGTM! Authorization skip correctly applied.The refactored authorization skip pattern correctly uses the per-instance authorization object with a closure.
2782-2789: LGTM! Authorization validation correctly refactored.The authorization validation pattern has been properly updated to use:
- Instance-based
$dbForProject->getAuthorization()->isValid(new Input(...))for validation$dbForProject->getAuthorization()->getDescription()for descriptive error messagesThis correctly implements the per-instance authorization approach.
2791-2797: LGTM! Authorization skip correctly applied for related document retrieval.The authorization skip patterns for fetching target and user documents are correctly implemented using the per-instance authorization object with closures.
2830-2834: LGTM! Authorization skip correctly applied for attribute modification.The authorization skip pattern for
increaseDocumentAttributeis properly implemented using the per-instance authorization object.
2886-2926: LGTM! Authorization skip patterns correctly applied.All authorization skip patterns in this section are correctly implemented:
- Topic, subscriber, target, and user document retrieval all properly use the per-instance authorization object
- Batch processing operations correctly maintain the authorization pattern within closures
3062-3075: LGTM! Authorization skip patterns consistently applied.The authorization skip patterns for topic, target, and user document retrieval are correctly implemented using the per-instance authorization approach.
3113-3141: LGTM! Authorization skip correctly applied for document operations.The authorization skip patterns for topic retrieval and
decreaseDocumentAttributeoperation are properly implemented using the per-instance authorization object.
3719-3719: LGTM! Authorization skip correctly applied.The authorization skip pattern for message document retrieval is properly implemented using the per-instance authorization object, consistent with the refactoring throughout the file.
…or, remove unused registers.php file, and clean up whitespace in cli.php and worker.php. Refactor database resource initialization in resources.php for consistency.
…th-single-instance # Conflicts: # composer.json # composer.lock
…nto refactor-auth-single-instance
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/cli.php (1)
24-24: Import DB Authorization (not validator) and reapply on reused DBs.
- Import the correct class so
disable()and DB wiring work.- When reusing a cached Database instance, reapply the current authorization to ensure roles/context propagate across tasks.
Apply:
- use Utopia\Database\Validator\Authorization; + use Utopia\Database\Authorization; @@ -CLI::setResource('authorization', function () { +CLI::setResource('authorization', function () { $authorization = new Authorization(); $authorization->disable(); - return $authorization; + return $authorization; }, []); @@ - if (isset($databases[$dsn->getHost()])) { - $database = $databases[$dsn->getHost()]; + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; + // Ensure current auth context is applied on reuse + $database->setAuthorization($authorization); $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));Also applies to: 60-65, 116-174, 176-203
app/worker.php (1)
32-32: Fix Authorization import and remove invalid adapter->setAuthorization() callThe import must be
Utopia\Database\Authorization(not the Validator class), andsetAuthorization()can only be called onDatabaseinstances, not on the adapter.Changes required at line 32 and line 152:
- use Utopia\Database\Validator\Authorization; + use Utopia\Database\Authorization; @@ - $adapter = new DatabasePool($pools->get($dsn->getHost())); - $adapter->setAuthorization($authorization); - $database = new Database($adapter, $cache); - $database->setAuthorization($authorization); + $adapter = new DatabasePool($pools->get($dsn->getHost())); + $database = new Database($adapter, $cache); + $database->setAuthorization($authorization);
♻️ Duplicate comments (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (1)
169-181: Fix Input casing and normalize permission source.
- Use
new Input(...)(capital “I”); currentnew input(...)will fatally error.- Prefer a consistent source for roles. Passing full
$resource->getPermissions()with the requested type toInputavoids API mismatches.Apply:
- $collectionValid = $dbForProject->getAuthorization()->isValid( - new input($permissionType, $collection->getPermissionsByType($permissionType)) - ); + $collectionValid = $dbForProject->getAuthorization()->isValid( + new Input($permissionType, $collection->getPermissions()) + ); @@ - if ($permissionType === Database::PERMISSION_UPDATE) { - $documentValid = $dbForProject->getAuthorization()->isValid( - new input(Database::PERMISSION_UPDATE, $document->getUpdate()) - ); - } elseif ($permissionType === Database::PERMISSION_DELETE) { - $documentValid = $dbForProject->getAuthorization()->isValid( - new input(Database::PERMISSION_DELETE, $document->getDelete()) - ); - } + if ($permissionType === Database::PERMISSION_UPDATE) { + $documentValid = $dbForProject->getAuthorization()->isValid( + new Input(Database::PERMISSION_UPDATE, $document->getPermissions()) + ); + } elseif ($permissionType === Database::PERMISSION_DELETE) { + $documentValid = $dbForProject->getAuthorization()->isValid( + new Input(Database::PERMISSION_DELETE, $document->getPermissions()) + ); + }If you intentionally rely on per-type getters (e.g.,
getUpdate()), keep them but still fix the casing:- new input(Database::PERMISSION_UPDATE, $document->getUpdate()) + new Input(Database::PERMISSION_UPDATE, $document->getUpdate())Run to catch any remaining issues:
#!/bin/bash # 1) Lowercase Input constructors rg -nP --type=php 'new\s+input\s*\(' # 2) Mixed permission APIs - audit availability rg -nP --type=php '\->getPermissionsByType\(|\->get(Update|Delete)\(\)|\->getPermissions\(\)' src -C2
🧹 Nitpick comments (1)
app/init/resources.php (1)
229-341: Consider injecting the Authorization into Request/Response early.If not already done upstream, call
$request->setAuthorization($authorization)and$response->setAuthorization($authorization)during bootstrap to keep the per-request context consistent. As per provided setters.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
app/cli.php(7 hunks)app/init/registers.php(0 hunks)app/init/resources.php(18 hunks)app/worker.php(11 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php(7 hunks)
💤 Files with no reviewable changes (1)
- app/init/registers.php
🧰 Additional context used
🧬 Code graph analysis (4)
app/cli.php (2)
src/Appwrite/Utopia/Request.php (1)
setAuthorization(239-242)src/Appwrite/Utopia/Response.php (1)
setAuthorization(983-986)
app/worker.php (3)
src/Appwrite/Utopia/Request.php (1)
setAuthorization(239-242)src/Appwrite/Utopia/Response.php (1)
setAuthorization(983-986)src/Appwrite/Auth/Auth.php (1)
getRoles(456-502)
app/init/resources.php (3)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)src/Appwrite/Utopia/Request.php (1)
setAuthorization(239-242)src/Appwrite/Utopia/Response.php (1)
setAuthorization(983-986)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (2)
src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php (1)
getGroupId(218-221)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: scan
🔇 Additional comments (1)
app/init/resources.php (1)
41-41: Review comment is incorrect; no issues found.The
Utopia\Database\Validator\Authorizationclass (imported at line 41) is the correct and only Authorization class available in the Utopia Database library. It has all required methods:skip(),getRoles(),hasRole(),setDefaultStatus(), anddisable(). The codebase extensively uses these methods across 100+ locations without issue, confirming the implementation is correct. No non-ValidatorUtopia\Database\Authorizationclass exists to migrate to.Likely an incorrect or invalid review comment.
…a-php/detector to version 0.2.*, and utopia-php/migration to version 1.3.3. Modify exception messages in storage API for better clarity on authorization failures. Adjust test assertions to reflect updated error messages.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
composer.json (1)
55-55: Remove development branch dependency for database package (persistent critical issue).This issue was flagged in the prior review but remains unresolved. Using
dev-feat-authorization-instancefor production code introduces instability. A stable release (3.0.2) is available and should be used instead.Change line 55 to use the stable release:
- "utopia-php/database": "dev-feat-authorization-instance as 3.0.2", + "utopia-php/database": "^3.0.2",If the stable 3.0.2 release does not include required authorization features, the feature branch must be merged into main, a new stable version released, and then pinned to that release before this PR can be merged.
Verify branch status:
#!/bin/bash # Confirm stable 3.0.2 is available gh api repos/utopia-php/database/releases --jq '.[] | select(.tag_name == "3.0.2")' # Check feature branch status gh api repos/utopia-php/database/branches/feat-authorization-instance --jq '{name, commit: .commit.sha}' # Check divergence gh api repos/utopia-php/database/compare/main...feat-authorization-instance --jq '{ahead_by, behind_by, status}'
🧹 Nitpick comments (2)
app/controllers/api/storage.php (1)
34-34: LGTM! Authorization refactor is well-executed.The transition from static Authorization methods to instance-based authorization accessed via
$dbForProject->getAuthorization()is consistent and correct. The Input validator import and usage are appropriate, and error messages now include descriptive details viagetDescription().Optional: Cache the authorization object for improved readability.
Throughout the file,
getAuthorization()is called multiple times in quick succession within the same endpoint. While this is not a bug (the method returns the same instance), caching it in a local variable would reduce repetition and improve readability:+$authorization = $dbForProject->getAuthorization(); -$bucket = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); +$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); -$isAPIKey = Auth::isAppUser($dbForProject->getAuthorization()->getRoles()); -$isPrivilegedUser = Auth::isPrivilegedUser($dbForProject->getAuthorization()->getRoles()); +$isAPIKey = Auth::isAppUser($authorization->getRoles()); +$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); -if (!$dbForProject->getAuthorization()->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { - throw new Exception(Exception::USER_UNAUTHORIZED, $dbForProject->getAuthorization()->getDescription()); +if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); }This pattern applies to many endpoints in the file (e.g., lines 789-801, 889-902, 970-995, 1161-1185, 1321-1345, 1474-1499, 1647-1660, 1760-1786).
Also applies to: 431-441
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (1)
174-181: Tidy doc-permission check (optional).Reduce duplication and make intent explicit with a small match-based selection.
- if ($document !== null && !$document->isEmpty() && $documentSecurity) { - if ($permissionType === Database::PERMISSION_UPDATE) { - $documentValid = $dbForProject->getAuthorization()->isValid( - new Input(Database::PERMISSION_UPDATE, $document->getUpdate()) - ); - } elseif ($permissionType === Database::PERMISSION_DELETE) { - $documentValid = $dbForProject->getAuthorization()->isValid( - new Input(Database::PERMISSION_DELETE, $document->getDelete()) - ); - } - } + if ($document !== null && !$document->isEmpty() && $documentSecurity) { + $docPerms = match ($permissionType) { + Database::PERMISSION_UPDATE => $document->getUpdate(), + Database::PERMISSION_DELETE => $document->getDelete(), + default => null, + }; + if ($docPerms !== null) { + $documentValid = $dbForProject->getAuthorization()->isValid( + new Input($permissionType, $docPerms) + ); + } + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
app/controllers/api/storage.php(23 hunks)composer.json(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php(7 hunks)tests/e2e/Services/Tokens/TokensBase.php(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/controllers/api/storage.php (5)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/unit/Messaging/MessagingChannelsTest.php (1)
getAuthorization(40-46)tests/unit/Auth/AuthTest.php (1)
getAuthorization(18-24)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (4)
src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)src/Appwrite/Databases/TransactionState.php (1)
getDocument(41-82)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php (1)
getGroupId(218-221)
🔇 Additional comments (7)
tests/e2e/Services/Tokens/TokensBase.php (1)
82-141: LGTM! Error message updates correctly reflect the new authorization system.The updated error message assertions are correct and represent an improvement. The new message "No permissions provided for action 'read'" is more specific and actionable than the previous generic message, clearly indicating:
- What's missing (permissions)
- The attempted action (read)
All four test scenarios (preview, custom preview, view, download) consistently use the same message, which is appropriate since they all test the same authorization check for anonymous users attempting read access.
app/controllers/api/storage.php (2)
697-700: Permission validation pattern is correct.The logic of validating CREATE permission before skipping authorization for document updates during chunked file uploads is sound. This ensures that users with create permission can complete chunked uploads even without explicit update permission, which is the intended behavior.
The pattern is consistently applied in both complete and incomplete upload scenarios.
Also applies to: 739-744
1866-1889: Usage statistics authorization handling looks good.The use of
skip()with closures to bypass authorization for internal statistics queries is appropriate and consistent with the refactor pattern. This ensures that usage stats can be retrieved without being filtered by the current user's permissions.Also applies to: 1962-1989
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (4)
75-82: Nice: consistent instance-based Authorization usage.Clean switch to $dbForProject->getAuthorization() including skip() around internal reads.
195-215: Permissions mutation guard looks good.Validates that requested $permissions roles are within caller’s roles; aligns with least-privilege.
169-181: API names verified—review comment is incorrect.All methods referenced in the code are valid and exist in
Utopia\Database\Document:getUpdate()(line 117),getDelete()(line 125), andgetPermissionsByType()(line 145). The same API patterns are used elsewhere in the codebase without issues. No runtime errors will occur.Likely an incorrect or invalid review comment.
95-103: Review comment valid but verification incomplete—plan structure is external to codebase.The key
databasesTransactionSizeis used consistently across 10 files, reducing likelihood of a typo. However, the$planarray is injected via DI container (external to this codebase), so we cannot definitively verify the key exists in the actual plan structure. The ?? fallback toAPP_LIMIT_DATABASE_TRANSACTIONprovides protection against missing keys. Manual verification needed to confirm the plan configuration includesdatabasesTransactionSizekey in the DI bootstrap/config layer.
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/controllers/api/migrations.php (1)
500-518: Incomplete migration to instance-based authorization.The CSV export action still uses static
Authorization::skip()calls (lines 500, 513, 518), while the CSV import action above was migrated to instance-based authorization. This inconsistency should be addressed to complete the refactoring.Apply this diff to complete the migration:
- $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));- $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('databases', $databaseId));- $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); + $collection = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/controllers/api/migrations.php(2 hunks)app/init/resources.php(18 hunks)app/worker.php(11 hunks)src/Appwrite/Utopia/Request/Filters/V20.php(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/Appwrite/Utopia/Request/Filters/V20.php (2)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)src/Appwrite/Databases/TransactionState.php (1)
getDocument(41-82)
app/worker.php (3)
src/Appwrite/Utopia/Request.php (1)
setAuthorization(239-242)src/Appwrite/Utopia/Response.php (1)
setAuthorization(983-986)src/Appwrite/Auth/Auth.php (1)
getRoles(456-502)
app/controllers/api/migrations.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (1)
action(69-252)src/Appwrite/Platform/Modules/Console/Http/Resources/Get.php (1)
action(64-139)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
app/init/resources.php (6)
tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)tests/unit/Messaging/MessagingChannelsTest.php (1)
getAuthorization(40-46)tests/unit/Auth/AuthTest.php (1)
getAuthorization(18-24)src/Appwrite/Utopia/Request.php (1)
setAuthorization(239-242)src/Appwrite/Utopia/Response.php (1)
setAuthorization(983-986)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: scan
🔇 Additional comments (12)
src/Appwrite/Utopia/Request/Filters/V20.php (2)
140-143: LGTM! Authorization refactoring correctly applied.The change from static
Authorization::skip()to instance-based$dbForProject->getAuthorization()->skip()is correct and consistent with the broader refactoring effort to use per-instance authorization objects.
152-155: LGTM! Consistent authorization pattern.The authorization refactoring is applied consistently with the same pattern used on line 140. Both calls appropriately bypass authorization to fetch metadata documents needed for the filter's backward compatibility logic.
app/controllers/api/migrations.php (1)
341-357: LGTM!The authorization refactoring is correctly implemented:
- Authorization checks use the instance-based
$dbForPlatform->getAuthorization()->getRoles()pattern- Proper guard added for internal file access requiring privileged user
- Authorization skip operations correctly scoped to the platform database instance
app/init/resources.php (6)
383-385: LGTM!The new
authorizationresource correctly creates a per-request Authorization instance with no dependencies, establishing the foundation for instance-based authorization throughout the application.
387-424: LGTM!The
dbForProjectresource correctly integrates per-request authorization:
- Accepts
Authorization $authorizationparameter- Propagates it to the database instance via
setAuthorization()- Properly declares the dependency
426-440: LGTM!The
dbForPlatformresource follows the same correct pattern asdbForProject, properly integrating per-request authorization.
442-494: LGTM!The
getProjectDBresource factory correctly propagates authorization through nested closures and sets it on database instances in all code paths.
496-522: LGTM!The
getLogsDBresource factory follows the same correct pattern, properly propagating authorization through the closure and setting it on the database instance.
203-1009: LGTM!All static
Authorization::skip()and authorization method calls have been correctly migrated to instance-based calls using the appropriate database context ($dbForPlatformor$dbForProject).app/worker.php (3)
52-56: LGTM!The worker's
authorizationresource correctly disables authorization by default, which is appropriate for background processing tasks that typically run with elevated privileges.
58-203: LGTM!All worker database resources (
dbForPlatform,dbForProject,getProjectDB,getLogsDB) correctly integrate per-request authorization following the same patterns as the main application resources. Authorization is properly set on both adapters and database instances where needed.
491-508: LGTM!The error handler correctly integrates instance-based authorization, enabling it to capture the authorization context (roles) in error logs using
$authorization->getRoles()instead of static calls.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (1)
20-30: LGTM! Proper per-instance Authorization implementation.The lazy initialization pattern correctly caches the Authorization instance, which is appropriate for per-instance usage in tests. This implementation is consistent with similar patterns in
AuthTest.phpandMessagingChannelsTest.php.Minor: The extra blank line at Line 27 can be removed for consistency:
$this->authorization = new Authorization(); - return $this->authorization; + return $this->authorization; } -
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php(5 hunks)tests/unit/Auth/AuthTest.php(7 hunks)tests/unit/Messaging/MessagingChannelsTest.php(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/unit/Auth/AuthTest.php
- tests/unit/Messaging/MessagingChannelsTest.php
🧰 Additional context used
🧬 Code graph analysis (1)
tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (4)
tests/unit/Auth/AuthTest.php (1)
getAuthorization(18-26)tests/unit/Messaging/MessagingChannelsTest.php (1)
getAuthorization(41-49)tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php (1)
getAuthorization(22-28)src/Appwrite/Auth/Auth.php (1)
getRoles(456-502)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: scan
🔇 Additional comments (2)
tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php (2)
127-128: LGTM! Correct conversion to instance-based Authorization.The refactoring correctly replaces static Authorization calls with instance-based calls:
getRoles()andcleanRoles()properly retrieve and clear roles- Using
addRole()in the restoration loop is semantically correct for adding multiple roles backThe pattern of saving roles, testing as a guest (no roles), and then restoring roles is appropriate for guest permission testing.
Also applies to: 149-151
161-162: LGTM! Consistent Authorization pattern.The same correct refactoring pattern is applied consistently in
testWriteRow, matching the implementation intestReadRows. The role management logic correctly saves, clears, and restores roles for guest permission testing.Also applies to: 237-239
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
tests/e2e/Services/Health/HealthCustomServerTest.php(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: scan
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/controllers/api/health.php(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/controllers/api/health.php (1)
src/Appwrite/Utopia/Response.php (1)
output(745-827)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: scan
…h-single-instance
…cy changes, including Symfony components and Utopia PHP libraries.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
.github/workflows/issue-triage.lock.yml (1)
1204-1205: Pin the mcp/fetch container image.Unpinned images are a supply‑chain risk and harm reproducibility. Use a version tag and preferably a digest.
- docker pull mcp/fetch + docker pull mcp/fetch:<version>@sha256:<digest> ... - "args": [ + "args": [ "run", "-i", "--rm", - "mcp/fetch" + "mcp/fetch:<version>@sha256:<digest>" ],Also applies to: 2044-2051
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
.github/workflows/issue-triage.lock.yml(29 hunks).github/workflows/issue-triage.md(5 hunks)composer.json(1 hunks)src/Appwrite/Platform/Modules/Functions/Workers/Builds.php(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- composer.json
🧰 Additional context used
🪛 actionlint (1.7.8)
.github/workflows/issue-triage.lock.yml
66-66: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
488-488: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
879-879: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
4688-4688: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
4816-4816: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
4867-4867: label "ubuntu-slim" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
🪛 LanguageTool
.github/workflows/issue-triage.md
[grammar] ~37-~37: Use a hyphen to join words.
Context: ...an issue comment to the issue with a one sentence analysis and move to the next i...
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
.github/workflows/issue-triage.md
73-73: Unordered list indentation
Expected: 0; Actual: 3
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 0; Actual: 3
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 0; Actual: 3
(MD007, ul-indent)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: scan
🔇 Additional comments (4)
.github/workflows/issue-triage.md (1)
3-5: Workflow trigger changes look good.The daily schedule plus manual trigger enables both automated and on-demand triage workflows, which is appropriate for issue management.
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (3)
1283-1283: LGTM—schedule update refactored correctly.The direct call to
$dbForPlatform->updateDocument('schedules', ...)removes theAuthorization::skip()wrapper, consistent with the PR's authorization refactoring. This administrative operation correctly updates function scheduling metadata after deployment completion.
1577-1581: LGTM—VCS rule lookup refactored correctly.The direct
findOnecall for VCS deployment rules removes theAuthorization::skip()wrapper, aligning with the PR's authorization refactoring. The operation correctly handles empty results at Line 1586, maintaining proper null safety for the preview URL generation.
924-928: Authorization refactoring verified—changes are correct.Verified that worker-level authorization is properly configured with
->disable()in app/worker.php:52-55, which is the appropriate approach for background job operations. All database instances receive this disabled authorization context, making the removal ofAuthorization::skip()wrappers functionally equivalent and architecturally cleaner. The 'screenshots' bucket exists and is already in use by other workers.
…-instance # Conflicts: # app/controllers/api/teams.php # composer.lock
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
app/controllers/api/account.php(49 hunks)app/controllers/api/teams.php(18 hunks)app/controllers/api/users.php(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T10:32:38.142Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10297
File: app/controllers/api/users.php:649-657
Timestamp: 2025-08-11T10:32:38.142Z
Learning: In Appwrite's utopia-php/database (version 0.77.* and later), the database layer internally handles filter separation for count operations. When using `$dbForProject->count()` within a `skipFilters` closure, there's no need to manually separate filter queries using `Query::groupByType($queries)['filters']` as the database handles this automatically.
Applied to files:
app/controllers/api/users.php
🧬 Code graph analysis (3)
app/controllers/api/users.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
action(80-178)src/Appwrite/Utopia/Response.php (1)
Response(158-987)
app/controllers/api/account.php (2)
src/Appwrite/Utopia/Request.php (1)
Request(13-243)src/Appwrite/Utopia/Response.php (1)
Response(158-987)
app/controllers/api/teams.php (3)
src/Appwrite/Utopia/Response.php (1)
Response(158-987)src/Appwrite/Auth/Auth.php (4)
isPrivilegedUser(421-432)Auth(18-515)getRoles(456-502)isAppUser(441-448)src/Appwrite/Utopia/Request.php (1)
Request(13-243)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: CodeQL
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: Linter
- GitHub Check: scan
🔇 Additional comments (8)
app/controllers/api/users.php (1)
2647-2683: LGTM! Per-request authorization correctly implemented.The getUsage endpoint has been successfully migrated to use instance-based Authorization via dependency injection. The
$authorization->skip()closure correctly bypasses authorization for internal stats queries, and the injection/signature alignment is proper.app/controllers/api/teams.php (7)
87-108: LGTM! Team creation correctly migrated to per-request authorization.The create team endpoint properly injects and uses instance-based Authorization. The privilege checks via
$authorization->getRoles()and the$authorization->skip()for team document creation are both correct.
493-682: LGTM! Membership creation correctly implements per-request authorization.The extensive changes in this endpoint correctly migrate to instance-based Authorization:
- Privilege checks use
$authorization->getRoles()(lines 502-503)- Owner validation uses
$authorization->hasRole()(line 621)- User creation bypasses authorization via
$authorization->skip()(lines 580-615)- Conditional document creation and attribute increments properly scope authorization (lines 657, 661, 674)
The logic flow is preserved and authorization scoping is appropriate for privileged vs. standard user operations.
861-938: LGTM! Membership listing correctly uses per-request authorization.The list memberships endpoint properly injects Authorization and uses
$authorization->getRoles()(line 932) to determine privacy settings for membership data. The migration maintains existing privilege-based filtering logic.
1003-1030: LGTM! Get membership endpoint correctly migrated.The get membership endpoint properly injects Authorization and uses
$authorization->getRoles()(line 1024) for privacy checks, consistent with the list endpoint pattern.
1103-1124: LGTM! Membership update correctly implements authorization checks.The update membership endpoint properly uses
$authorization->getRoles()for privilege checks (lines 1122-1123) and$authorization->hasRole()for owner validation (line 1124), maintaining correct access control logic.
1205-1319: LGTM! Membership status update correctly manages authorization state.This endpoint demonstrates proper authorization patterns:
$authorization->skip()retrieves team info bypassing checks (line 1218)$authorization->skip()updates email verification (line 1254)$authorization->addRole()grants team role during membership confirmation (lines 1258, 1286)$authorization->skip()increments team member count (line 1319)The dynamic role addition during membership acceptance is the correct approach for granting team access within the confirmation flow.
1363-1423: LGTM! Membership deletion correctly uses authorization skip.The delete membership endpoint properly injects Authorization and uses
$authorization->skip()(line 1423) to decrement the team member count, appropriately bypassing authorization for this system-level attribute update.
…th-single-instance # Conflicts: # composer.lock
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php (1)
14-15: Fix namespace collision and add missing import for Authorization.Line 14 imports
Utopia\Database\Validator\Authorization, but this conflicts with theAuthorizationclass being injected on line 78 (which should be the core authorization class, likelyUtopia\Database\Authorization). The validator import also appears unused in this file.Apply this diff to resolve the namespace conflict:
-use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; +use Utopia\Database\Authorization;If the validator is needed elsewhere, alias one of the imports:
-use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization as AuthorizationValidator; use Utopia\Database\Validator\UID; +use Utopia\Database\Authorization;src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
77-119: Authorization refactoring incomplete—static call remains at Update.php:169.The refactoring to inject
Authorizationas a dependency was not fully completed. Theaction()method inUpdate.phpreceives theAuthorizationparameter (line 106) and correctly uses$authorization->skip()at line 119, but line 169 still uses the staticAuthorization::skip()call instead of the injected instance.Fix required:
- File:
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php- Line 169: Change
Authorization::skip(to$authorization->skip(app/controllers/api/messaging.php (2)
2503-2516: Cursor value must be a Document, not $cursorDocument[0].Using index [0] breaks pagination. Set the cursor to the fetched Document.
- $cursor->setValue($cursorDocument[0]); + $cursor->setValue($cursorDocument);
2315-2351: APNS update: credentials key mismatch prevents enabling.You set credentials['bundle'] but later check for credentials['bundleId'], making enablement impossible.
- if (!empty($bundleId)) { - $credentials['bundle'] = $bundleId; - } + if (!empty($bundleId)) { + $credentials['bundleId'] = $bundleId; + }app/controllers/general.php (1)
927-998: Always re-enable authorization (try/finally)If an exception occurs in this block, authorization may remain disabled for the request. Guard with try/finally.
- $authorization->disable(); - - $envDomain = System::getEnv('_APP_DOMAIN', ''); - $mainDomain = null; - if (!empty($envDomain) && $envDomain !== 'localhost') { - $mainDomain = $envDomain; - } else { + $authorization->disable(); + try { + $envDomain = System::getEnv('_APP_DOMAIN', ''); + $mainDomain = null; + if (!empty($envDomain) && $envDomain !== 'localhost') { + $mainDomain = $envDomain; + } else { // TODO: @christyjacob remove once we migrate the rules in 1.7.x if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { $domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain)); } else { $domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]); } - } - $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); + } + $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); ... - $domains[$domain->get()] = true; - - $authorization->reset(); // ensure authorization is re-enabled + $domains[$domain->get()] = true; + } finally { + $authorization->reset(); // ensure authorization is re-enabled + }src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (1)
168-171: Replace static Authorization::skip with the injected instance for consistencyAt line 169,
Authorization::skip()is the only static call of this method in the file. The$authorizationinstance is injected as a parameter (line 106) and passed into the closure'suseclause (line 141), making it available. All other calls in this file (lines 142, 146, 234, 246, 251, 256, 261, 266, 271, 299, 303, 395) use the injected instance.if (!isset($collections[$collectionId])) { - $collections[$collectionId] = Authorization::skip( + $collections[$collectionId] = $authorization->skip( fn () => $dbForProject->getCollection($collectionId) ); }src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (1)
86-86: Remove unused$requestTimestampparameter from injection and method signature.The parameter is injected and included in the method signature but never used in the method body. Unlike similar methods in Update.php (line 320) and Delete.php (line 190) that call
withRequestTimestamp(), Upsert.php has no such usage. Either remove the parameter or add the missing logic to use it.
♻️ Duplicate comments (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (1)
264-284: Fix UPDATE permission logic with documentSecurity=false (re-break).When permission=UPDATE and documentSecurity=false, the current condition always throws even if collection UPDATE is allowed. Align with the transactions operations pattern: require collection perms when documentSecurity=false; otherwise allow if either collection OR document perms are valid.
Apply:
- $validCollection = $authorization->isValid( - new Input($permission, $collection->getPermissionsByType($permission)) - ); - if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$validCollection) { - throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); - } - - if ($permission === Database::PERMISSION_UPDATE) { - $validDocument = $authorization->isValid( - new Input($permission, $document->getUpdate()) - ); - $valid = $validCollection || $validDocument; - if ($documentSecurity && !$valid) { - throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); - } - } + $validCollection = $authorization->isValid( + new Input($permission, $collection->getPermissionsByType($permission)) + ); + + if ($permission === Database::PERMISSION_UPDATE) { + $validDocument = $authorization->isValid( + new Input(Database::PERMISSION_UPDATE, $document->getUpdate()) + ); + if (!$documentSecurity) { + if (!$validCollection) { + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); + } + } else { + if (!$validCollection && !$validDocument) { + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); + } + } + } else { // CREATE + if (!$validCollection) { + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); + } + }app/controllers/general.php (1)
1329-1329: Don’t log raw roles; log coarse flags/count insteadRoles may contain user/team IDs (PII). Log non-identifying flags.
- $log->addExtra('roles', $authorization->getRoles()); + $roles = $authorization->getRoles(); + $log->addExtra('roleFlags', [ + 'isPrivileged' => Auth::isPrivilegedUser($roles), + 'isAppUser' => Auth::isAppUser($roles), + 'count' => count($roles), + ]);app/controllers/api/migrations.php (1)
345-349: Include API keys in internal file access guardAllow API keys as well as privileged users; otherwise valid API key calls will be rejected.
- if ($internalFile && !$isPrivilegedUser) { + if ($internalFile && !$isAPIKey && !$isPrivilegedUser) { throw new Exception(Exception::USER_UNAUTHORIZED); }
🧹 Nitpick comments (4)
app/controllers/shared/api.php (1)
585-616: Minor: avoid re-computing $isPrivilegedUser.$roles and $isPrivilegedUser are computed earlier; reuse them to reduce duplication.
Consider removing the second assignment:
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + // reuse previously computed $isPrivilegedUserapp/controllers/api/users.php (1)
2661-2684: Tiny cleanup: drop unused loop index$count isn’t used. Iterate directly over $metrics.
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { - foreach ($metrics as $count => $metric) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + foreach ($metrics as $metric) {app/controllers/general.php (1)
60-60: Unused parameter: $utopiarouter() doesn’t use $utopia. Consider prefixing with _ or removing to silence PHPMD.
-function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Authorization $authorization, ?Key $apiKey) +function router(App $_, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Authorization $authorization, ?Key $apiKey)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (1)
106-107: Remove unused $user parameter from action() method signatureThe
$userparameter (Document $user) is unused in the action() method. Verification confirms:
- Ripgrep search found $user only in the function signature and PHPDoc comment—no usage anywhere in the file
- The main transaction processing closure at line 141 explicitly lists captured variables in its
use()clause and notably omits$user- No indirect references through closures or callbacks
Removing this parameter will reduce the dependency injection surface as suggested. Update line 106 to remove
Document $user,from the parameter list.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (82)
app/controllers/api/messaging.php(17 hunks)app/controllers/api/migrations.php(7 hunks)app/controllers/api/project.php(3 hunks)app/controllers/api/storage.php(25 hunks)app/controllers/api/users.php(2 hunks)app/controllers/general.php(21 hunks)app/controllers/shared/api.php(15 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php(4 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php(4 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php(10 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php(5 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php(9 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php(10 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php(3 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php(8 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Enum/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Integer/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php(1 hunks)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php(9 hunks)src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php(3 hunks)src/Appwrite/Platform/Modules/Functions/Workers/Builds.php(5 hunks)src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php(4 hunks)src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php(4 hunks)src/Appwrite/Platform/Workers/Deletes.php(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php
🚧 Files skipped from review as they are similar to previous changes (35)
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Line/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Polygon/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Point/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/URL/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Boolean/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/IP/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Float/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Polygon/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Update.php
- app/controllers/api/project.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Relationship/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Datetime/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-05-10T04:12:13.037Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 9693
File: src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php:85-86
Timestamp: 2025-05-10T04:12:13.037Z
Learning: The class `Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes\Action` defines a method `getGrantParentNotFoundException()` which is used to handle cases where a parent resource (like a database collection) is not found.
Applied to files:
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.phpapp/controllers/api/migrations.php
📚 Learning: 2025-10-02T10:16:48.075Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10023
File: src/Appwrite/Databases/TransactionState.php:174-219
Timestamp: 2025-10-02T10:16:48.075Z
Learning: In `src/Appwrite/Databases/TransactionState.php`, the `listDocuments` method intentionally applies queries only to the initial database fetch of committed documents. Transaction state changes (creates, updates, deletes) are then overlaid on top of the filtered committed set without re-evaluating the queries against the final merged result. This is the intended behavior for transaction-aware document listing.
Applied to files:
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
📚 Learning: 2025-06-19T09:20:03.312Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 9693
File: src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Update.php:57-59
Timestamp: 2025-06-19T09:20:03.312Z
Learning: In table-related endpoints (such as `src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Update.php`), parameter descriptions should use "table" and "row" terminology instead of "collection" and "document" for clarity and consistency.
Applied to files:
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.
Applied to files:
src/Appwrite/Platform/Workers/Deletes.php
📚 Learning: 2025-05-13T12:02:08.071Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 9752
File: app/config/collections/platform.php:1423-1423
Timestamp: 2025-05-13T12:02:08.071Z
Learning: In the Appwrite database schema, field names like 'resourceInternalId'/'resourceType' and 'deploymentInternalId'/'deploymentResourceType' are functionally equivalent and can coexist in the codebase without causing errors. The docker build and stack work fine with these naming inconsistencies.
Applied to files:
app/controllers/general.php
📚 Learning: 2025-08-11T10:32:38.142Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10297
File: app/controllers/api/users.php:649-657
Timestamp: 2025-08-11T10:32:38.142Z
Learning: In Appwrite's utopia-php/database (version 0.77.* and later), the database layer internally handles filter separation for count operations. When using `$dbForProject->count()` within a `skipFilters` closure, there's no need to manually separate filter queries using `Query::groupByType($queries)['filters']` as the database handles this automatically.
Applied to files:
app/controllers/api/users.phpapp/controllers/api/messaging.phpapp/controllers/api/storage.php
🧬 Code graph analysis (30)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Databases/TransactionState.php (2)
TransactionState(20-747)getDocument(44-85)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Update.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php (1)
action(76-94)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Update.php (1)
action(80-110)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php (6)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (1)
action(98-390)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php (1)
action(90-238)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
action(81-179)src/Appwrite/Databases/TransactionState.php (2)
TransactionState(20-747)getDocument(44-85)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php (1)
action(76-94)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (5)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (1)
action(98-390)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (1)
action(139-515)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php (1)
action(77-140)src/Appwrite/Databases/TransactionState.php (2)
TransactionState(20-747)getDocument(44-85)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php (2)
src/Appwrite/Databases/TransactionState.php (1)
getDocument(44-85)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php (1)
action(77-91)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php (1)
action(82-116)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php (1)
action(82-118)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php (1)
action(85-147)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (1)
action(106-409)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (1)
action(71-254)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php (1)
action(85-139)
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)
src/Appwrite/Databases/TransactionState.php (1)
getDocument(44-85)
src/Appwrite/Platform/Workers/Deletes.php (1)
src/Appwrite/Databases/TransactionState.php (1)
getDocument(44-85)
app/controllers/api/migrations.php (3)
src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)src/Appwrite/Utopia/Response.php (2)
Response(158-987)file(838-845)src/Appwrite/Migration/Migration.php (1)
Migration(18-450)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
src/Appwrite/Auth/Auth.php (4)
Auth(18-515)isAppUser(441-448)getRoles(456-502)isPrivilegedUser(421-432)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php (1)
action(77-91)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php (1)
action(82-116)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php (1)
action(76-94)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php (1)
action(77-96)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Point/Update.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php (8)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php (1)
action(77-91)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php (1)
action(78-102)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php (1)
action(81-110)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php (1)
action(82-116)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php (1)
action(82-118)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php (1)
action(90-163)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php (1)
action(85-147)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php (1)
action(78-104)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php (1)
action(78-116)
app/controllers/general.php (3)
src/Appwrite/Utopia/Request.php (1)
Request(13-243)src/Appwrite/Utopia/Response.php (1)
Response(158-987)src/Appwrite/Auth/Auth.php (3)
Auth(18-515)isPrivilegedUser(421-432)getRoles(456-502)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Create.php (8)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Create.php (1)
action(77-91)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php (1)
action(78-102)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Enum/Create.php (1)
action(81-110)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/IP/Create.php (1)
action(78-102)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Create.php (1)
action(82-118)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Relationship/Create.php (1)
action(90-163)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/String/Create.php (1)
action(85-147)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/URL/Create.php (1)
action(78-104)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Databases/TransactionState.php (2)
TransactionState(20-747)getDocument(44-85)
app/controllers/api/users.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
action(81-179)src/Appwrite/Utopia/Response.php (1)
Response(158-987)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Integer/Update.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php (1)
action(76-94)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Float/Update.php (1)
action(79-105)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Datetime/Update.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Boolean/Update.php (1)
action(76-94)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
app/controllers/api/messaging.php (2)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(102-512)src/Appwrite/Utopia/Response.php (1)
Response(158-987)
app/controllers/api/storage.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php (1)
action(81-179)src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/XList.php (1)
action(65-95)src/Appwrite/Utopia/Response.php (3)
file(838-845)Response(158-987)output(745-827)src/Appwrite/Utopia/Request.php (1)
Request(13-243)
app/controllers/shared/api.php (3)
src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Create.php (1)
action(73-112)src/Appwrite/Utopia/Request.php (2)
Request(13-243)cacheIdentifier(230-235)src/Appwrite/Auth/Auth.php (4)
Auth(18-515)getRoles(456-502)isPrivilegedUser(421-432)isAppUser(441-448)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Line/Update.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Action.php (1)
updateAttribute(475-682)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php
98-98: Avoid unused parameters such as '$requestTimestamp'. (undefined)
(UnusedFormalParameter)
app/controllers/general.php
60-60: Avoid unused parameters such as '$utopia'. (undefined)
(UnusedFormalParameter)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
106-106: Avoid unused parameters such as '$user'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: scan-pr / osv-scan
- GitHub Check: CodeQL
- GitHub Check: Linter
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Appwrite/Platform/Workers/Migrations.php (1)
22-22: Critical: Type ambiguity due to naming conflict.Line 22 imports
Utopia\Database\Validator\Authorization, which creates a naming conflict with the Authorization resource class that should be injected. The unqualifiedAuthorizationtype hints at lines 105, 301, and 439 now resolve to the validator class instead of the authorization resource class. Line 453 calls$authorization->skip(...), which doesn't exist on the validator class and will cause a runtime error.Apply this diff to resolve the conflict by importing the Authorization resource class and aliasing the validator:
-use Utopia\Database\Validator\Authorization; +use Utopia\Database\Authorization; +use Utopia\Database\Validator\Authorization as AuthorizationValidator;If the validator is no longer used in this file, simply remove the validator import:
-use Utopia\Database\Validator\Authorization; +use Utopia\Database\Authorization;Also applies to: 105-105, 301-301, 439-439
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php(9 hunks)src/Appwrite/Platform/Workers/Migrations.php(7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.
Applied to files:
src/Appwrite/Platform/Workers/Migrations.php
📚 Learning: 2025-10-02T10:16:48.075Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10023
File: src/Appwrite/Databases/TransactionState.php:174-219
Timestamp: 2025-10-02T10:16:48.075Z
Learning: In `src/Appwrite/Databases/TransactionState.php`, the `listDocuments` method intentionally applies queries only to the initial database fetch of committed documents. Transaction state changes (creates, updates, deletes) are then overlaid on top of the filtered committed set without re-evaluating the queries against the final merged result. This is the intended behavior for transaction-aware document listing.
Applied to files:
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
🧬 Code graph analysis (2)
src/Appwrite/Platform/Workers/Migrations.php (1)
src/Appwrite/Event/Mail.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (6)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)
action(94-366)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (1)
action(139-515)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php (2)
action(90-238)Delete(26-239)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Create.php (1)
action(62-88)src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php (1)
action(71-254)src/Appwrite/Databases/TransactionState.php (2)
TransactionState(20-747)getDocument(44-85)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
106-106: Avoid unused parameters such as '$user'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: E2E Service Test (Messaging)
- GitHub Check: E2E Service Test (VCS)
- GitHub Check: E2E Service Test (Locale)
- GitHub Check: E2E Service Test (Sites)
- GitHub Check: E2E Service Test (Teams)
- GitHub Check: E2E Service Test (Tokens)
- GitHub Check: E2E Service Test (FunctionsSchedule)
- GitHub Check: E2E Service Test (Console)
- GitHub Check: E2E Service Test (Account)
- GitHub Check: E2E Service Test (GraphQL)
- GitHub Check: E2E Service Test (Databases/Legacy)
- GitHub Check: E2E Shared Mode Service Test (Site Screenshots) (Shared V1)
- GitHub Check: E2E Service Test (Site Screenshots)
- GitHub Check: E2E Shared Mode Service Test (Site Screenshots) (Shared V2)
- GitHub Check: E2E Shared Mode Service Test (Dev Keys) (Shared V1)
- GitHub Check: E2E Shared Mode Service Test (Dev Keys) (Shared V2)
- GitHub Check: Unit Test
- GitHub Check: E2E General Test
- GitHub Check: Benchmark
- GitHub Check: scan
🔇 Additional comments (4)
src/Appwrite/Platform/Workers/Migrations.php (2)
87-87: Authorization threading implemented correctly.The authorization dependency is properly threaded through the call chain: injected in the constructor (line 87), received in
action(line 105), passed toprocessMigration(line 132), received there (line 301), and forwarded tohandleCSVExportComplete(lines 417, 438-439). This follows the dependency injection pattern consistently.Also applies to: 105-105, 132-132, 301-301, 417-417, 438-439
453-453: Appropriate use of authorization skip for privileged worker access.Using
$authorization->skip()here is correct since the migration worker needs privileged access to retrieve the bucket document from the platform database, bypassing per-request authorization checks.src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php (2)
79-79: LGTM! Authorization injection added correctly.The Authorization dependency is properly injected following the same pattern used in other transaction endpoints.
115-116: LGTM! Authorization refactoring implemented correctly.The transition from static Authorization usage to the injected per-request instance is consistent and complete throughout:
- Authorization checks properly use
$authorization->getRoles()(lines 115-116)- Privileged operations correctly use
$authorization->skip()for bypassing permission checks- The
$authorizationinstance is properly captured in the transaction closure (line 141)- All error handling paths consistently use
$authorization->skip()to update transaction status to 'failed'- Event processing uses
$authorization->skip()for database/collection lookupsThis matches the pattern established in other transaction endpoints and aligns with the PR objectives.
Also applies to: 119-119, 141-142, 146-146, 169-169, 234-234, 246-246, 251-251, 256-256, 261-261, 266-266, 271-271, 299-299, 303-303, 395-395
src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
Show resolved
Hide resolved
…nstance Revert "Merge pull request #10682 from appwrite/refactor-auth-single-…
…-single-instance"" This reverts commit 8765d56.
No description provided.