perf: optimize updateDocument() calls to use sparse documents#11465
perf: optimize updateDocument() calls to use sparse documents#11465ChiragAgg5k merged 2 commits into1.8.xfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR replaces numerous updateDocument() calls that previously passed whole entity objects with new Utopia\Database\Document instances containing only the changed fields (sparse/partial updates). Changes touch controllers, platform modules, workers, initialization code, and documentation (added AGENTS.md section and CLAUDE.md). Control flow, error handling, and public interfaces are unchanged; several files add imports for Utopia\Database\Document. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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! |
4fcf6e3 to
b4a2d52
Compare
🔄 PHP-Retry SummaryFlaky tests detected across commits: Commit
|
| Test | Retries | Total Time | Details |
|---|---|---|---|
UsageTest::testDatabaseStatsCollectionsAPI |
1 | 10.28s | Logs |
LegacyConsoleClientTest::testNotBetween |
1 | 240.34s | Logs |
TablesDBConsoleClientTest::testCreateIndexes |
1 | 242.55s | Logs |
TablesDBCustomClientTest::testCreateAttributes |
1 | 240.87s | Logs |
TablesDBTransactionsCustomServerTest::testDeleteDocumentDuringTransaction |
1 | 240.45s | Logs |
Commit f2ec1b1 - 3 flaky tests
| Test | Retries | Total Time | Details |
|---|---|---|---|
UsageTest::testDatabaseStatsCollectionsAPI |
1 | 10.24s | Logs |
TablesDBConsoleClientTest::testInvalidDocumentStructure |
1 | 240.84s | Logs |
TablesDBTransactionsConsoleClientTest::testOperationsOnNonExistentDocuments |
1 | 240.39s | Logs |
Commit 99dda0e - 1 flaky test
| Test | Retries | Total Time | Details |
|---|---|---|---|
UsageTest::testDatabaseStatsCollectionsAPI |
1 | 10.24s | Logs |
Commit 8b026d3 - 8 flaky tests
| Test | Retries | Total Time | Details |
|---|---|---|---|
UsageTest::testDatabaseStatsCollectionsAPI |
1 | 10.29s | Logs |
LegacyCustomClientTest::testInvalidDocumentStructure |
1 | 241.49s | Logs |
TablesDBConsoleClientTest::testAttributeResponseModels |
1 | 242.13s | Logs |
TablesDBCustomClientTest::testUpdateAttributeEnum |
1 | 240.38s | Logs |
TablesDBCustomClientTest::testNotStartsWith |
1 | 240.66s | Logs |
TablesDBCustomClientTest::testUpdatedBefore |
1 | 240.88s | Logs |
TablesDBTransactionsCustomClientTest::testMixedSingleOperations |
1 | 240.84s | Logs |
TablesDBTransactionsCustomClientTest::testBulkDeleteMatchingUpdatedDocuments |
1 | 240.83s | Logs |
Commit f282618 - 2 flaky tests
| Test | Retries | Total Time | Details |
|---|---|---|---|
UsageTest::testDatabaseStatsCollectionsAPI |
1 | 10.22s | Logs |
TablesDBConsoleClientTest::testBulkOperators |
1 | 240.55s | Logs |
Note: Flaky test results are tracked for the last 5 commits
✨ Benchmark results
⚡ Benchmark Comparison
|
There was a problem hiding this comment.
Pull request overview
This PR applies a performance optimization across 58+ files to replace full Document objects with sparse Document objects in updateDocument() calls, passing only the changed attributes. The updateDocument() method internally performs array_merge($old, $new), so sending fewer fields reduces overhead. Additionally, AGENTS.md is updated to document this pattern, and a CLAUDE.md file is added to reference AGENTS.md.
Changes:
- Replaced full-document
updateDocument()calls with sparse Documents across workers, HTTP modules, and controller files - Added "Performance Patterns" section to AGENTS.md documenting the optimization
- Added CLAUDE.md referencing AGENTS.md for AI agent context
Reviewed changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
src/Appwrite/Platform/Workers/Builds.php |
Sparse document updates for deployment state; critical data loss bug introduced |
src/Appwrite/Platform/Workers/Webhooks.php |
Sparse updates for logs/enabled and attempts reset |
src/Appwrite/Platform/Workers/Migrations.php |
Replaced SET_TYPE_APPEND with manual array append + sparse update |
src/Appwrite/Platform/Workers/Messaging.php |
Sparse update for message delivery fields |
src/Appwrite/Platform/Workers/Certificates.php |
Sparse update for rule status/certificateId/logs |
src/Appwrite/Platform/Tasks/Interval.php |
Sparse update for stale execution status |
src/Appwrite/Platform/Modules/VCS/** |
Sparse updates for VCS repository/installation fields |
src/Appwrite/Platform/Modules/Teams/** |
Sparse updates for membership/team fields |
src/Appwrite/Platform/Modules/Sites/** |
Sparse updates for site/deployment/variable fields |
src/Appwrite/Platform/Modules/Projects/** |
Sparse updates for project/team/label fields |
src/Appwrite/Platform/Modules/Functions/** |
Sparse updates across deployments, variables, schedules |
src/Appwrite/Platform/Modules/Databases/** |
Sparse updates for attribute/index status fields |
src/Appwrite/Platform/Modules/Compute/Base.php |
Sparse updates for function/site latestDeployment fields |
src/Appwrite/Platform/Modules/Account/** |
Sparse updates for MFA, session, and user fields |
app/controllers/api/account.php |
Sparse updates across account endpoints |
app/controllers/api/users.php |
Sparse updates for user CRUD operations |
app/controllers/api/messaging.php |
Sparse update for topic fields |
app/controllers/shared/api.php |
Sparse updates for accessedAt/cache fields |
app/controllers/general.php |
Sparse update for project ping fields |
app/init/resources.php |
Sparse updates for devKey/resourceToken accessedAt |
app/realtime.php |
Sparse update for realtime stats document |
AGENTS.md |
Added "Performance Patterns" documentation section |
CLAUDE.md |
New file referencing AGENTS.md |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
Outdated
Show resolved
Hide resolved
src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
Show resolved
Hide resolved
src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
Show resolved
Hide resolved
b4a2d52 to
082a875
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (4)
src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php (1)
67-71: RedundantsetAttributecalls can be removed.Lines 67-69 modify
$team, but those mutations are never used—line 71 creates a fresh sparseDocumentwith the same values and reassigns the result back to$team. ThesetAttributecalls are now dead code.♻️ Proposed fix to remove redundant code
- $team - ->setAttribute('name', $name) - ->setAttribute('search', implode(' ', [$teamId, $name])); - $team = $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php` around lines 67 - 71, Remove the redundant mutations: delete the $team->setAttribute('name', $name) and $team->setAttribute('search', ...) calls since they are not used before $team is reassigned by dbForProject->updateDocument; keep the update call that creates the new sparse Document (new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])])) and assign its result back to $team, ensuring only the updateDocument invocation and Document construction remain.src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php (1)
103-108: Build each sparse patch once and reuse it.These blocks duplicate the updated field list in two places: first via
setAttribute(...), then again innew Document([...]). That makes future edits easy to miss in one path and can desync the in-memory response object from the persisted payload. A small$variablePatch/$schedulePatcharray reused for both steps would keep them aligned.♻️ Example refactor
- $variable - ->setAttribute('key', $key) - ->setAttribute('value', $value ?? $variable->getAttribute('value')) - ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) - ->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function'])); + $variablePatch = [ + 'key' => $key, + 'value' => $value ?? $variable->getAttribute('value'), + 'secret' => $secret ?? $variable->getAttribute('secret'), + 'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']), + ]; + + foreach ($variablePatch as $attribute => $patchValue) { + $variable->setAttribute($attribute, $patchValue); + } try { - $dbForProject->updateDocument('variables', $variable->getId(), new Document([ - 'key' => $key, - 'value' => $value ?? $variable->getAttribute('value'), - 'secret' => $secret ?? $variable->getAttribute('secret'), - 'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']), - ])); + $dbForProject->updateDocument('variables', $variable->getId(), new Document($variablePatch)); } catch (DuplicateException $th) { throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); }Apply the same pattern to the schedule update block.
Also applies to: 122-126
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php` around lines 103 - 108, The variable update duplicates the fields in setAttribute(...) and in the new Document([...]) passed to $dbForProject->updateDocument; create a single associative array (e.g. $variablePatch) containing 'key', 'value', 'secret', and 'search' (constructed the same way as currently done with $variableId, $function->getId(), $key, 'function'), then pass that array to new Document($variablePatch) for the DB update and call $variable->setAttribute(...) with the same $variablePatch values so both in-memory and persisted representations come from the same source; apply the same pattern for the schedule update block (use $schedulePatch) to avoid duplication and keep them in sync.src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php (1)
150-154: Minor:DateTime::now()called twice may produce slightly different timestamps.Line 152 sets
mfaUpdatedAttoDateTime::now(), but line 154 creates anothernew Documentwith a freshDateTime::now()call. While typically negligible (same second), consider reusing the value for consistency:♻️ Suggested improvement
+ $mfaUpdatedAt = DateTime::now(); $session ->setAttribute('factors', $factors) - ->setAttribute('mfaUpdatedAt', DateTime::now()); + ->setAttribute('mfaUpdatedAt', $mfaUpdatedAt); - $dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' => DateTime::now()])); + $dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' => $mfaUpdatedAt]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php` around lines 150 - 154, The code calls DateTime::now() twice causing potential minor timestamp drift; capture the current timestamp once into a variable (e.g. $now) and reuse it for both $session->setAttribute('mfaUpdatedAt', ...) and the new Document(['mfaUpdatedAt' => ...]) used in $dbForProject->updateDocument to ensure the session attribute and stored document share the identical timestamp; update references to DateTime::now() in the Account MFA update block accordingly (affecting $session, setAttribute, and the Document passed to updateDocument).AGENTS.md (1)
54-78: Add an exception for atomic/read-modify-write updates.Sparse documents are a good perf optimization, but they do not make counters or append-style array updates concurrency-safe. Without that caveat, this guidance is likely to be copied into places where concurrent requests can still drop writes.
📝 Suggested doc tweak
**Exceptions:** +- Read/modify/write updates on counters or append-style arrays. Prefer atomic helpers + (for example `increaseDocumentAttribute()`) or another concurrency-safe pattern. - Migration files (need full document updates by design) - Cases already using `array_merge()` with `getArrayCopy()` - Updates with 6+ attributes changing simultaneously (marginal benefit) - Complex nested relationship logic where full document state is required🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` around lines 54 - 78, Add an exception to the guidance that sparse Document updates are not appropriate for atomic/read-modify-write operations (e.g., counters or append-style array updates) where concurrency-safety is required; reference updateDocument(), Document, array_merge(), and getArrayCopy() and state that in these cases callers should use DB-provided atomic operations or a full-document update/locking strategy instead of a sparse Document to ensure correctness under concurrent requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/controllers/api/account.php`:
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' into $dbForProject->updateDocument
which can set phoneVerification to null; instead, construct the Document payload
to include only the verification flags that are present/changed on the $user
(check via $user->hasAttribute(...) or by testing getAttribute(...) !==
null/using whatever "dirty" API exists), e.g. add 'emailVerification' only if
present/changed and add 'phoneVerification' only if present/changed, then call
$dbForProject->updateDocument('users', $user->getId(), new Document($payload))
only when $payload is non-empty so you don't overwrite sparse fields
unintentionally.
- Around line 4521-4525: The update is always writing the 'expired' attribute
from $target back to the DB which can persist null/incorrect values because
createPushTarget() doesn't initialize it; change the updateDocument call (and
the code that prepares its payload) to only include 'expired' when the incoming
request actually supplies or changes that value — e.g., build a $data array with
'identifier' and 'name', then if $target->getAttribute('expired') is set/ !==
null or differs from the stored value add 'expired' to $data before calling
$dbForProject->updateDocument('targets', $target->getId(), new Document($data));
ensure updateDocument and createPushTarget() behavior remain compatible.
In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The code in Create.php uses new Document(...) inside the
updateDocument call (in the Duplicate/Create deployment flow) but lacks the
import for Utopia\Database\Document; add a use statement for
Utopia\Database\Document near the other imports (so the instantiation in the
updateDocument call succeeds) and ensure there are no naming conflicts with any
existing Document classes in that file.
In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php`:
- Around line 122-125: The code calls new Document([...]) inside the
$authorization->skip closure when calling
$dbForPlatform->updateDocument('schedules', $schedule->getId(), ...) but the
Utopia\Database\Document class is not imported; add the import for
Utopia\Database\Document alongside the other Utopia\Database imports at the top
of the file (or replace new Document(...) with the fully-qualified
\Utopia\Database\Document) so the instantiation in the Delete.php schedule
update succeeds without a fatal "class not found" error.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php`:
- Around line 286-291: The update call reassigns $function to a sparse Document
with only the four passed fields, losing other attributes like installationId;
fix by passing a merged array of the current full document and the sparse
updates to updateDocument — e.g., use array_merge($function->getArrayCopy(), [
'scheduleId' => ..., 'scheduleInternalId' => ..., 'repositoryId' => ...,
'repositoryInternalId' => ... ]) when creating the new Document so $function
retains its full state after updateDocument and subsequent calls to
$function->getAttribute(...) work correctly.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: Add the missing import for the Utopia Database Document
class so new Document([...]) resolves; update the top of Delete.php to import
Utopia\Database\Document. Locate the block where $authorization->skip calls
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...])) and ensure the Document class is imported so instantiating
Document does not cause a "Class not found" fatal error.
In `@src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php`:
- Around line 79-81: The call new Document([...]) in Delete.php (inside the
updateDocument call that sets 'live' => false) is resolving to the current
namespace and causing a fatal error; fix it by importing the correct Document
class with a use statement (e.g., add the correct Document FQCN at the top of
the file) or by fully-qualifying the class in-place (e.g., replace new Document
with new \Fully\Qualified\DocumentClassName) so that the Document referenced is
the database Document type used by updateDocument.
In `@src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php`:
- Around line 96-101: The updateDocument calls in the Update class are
instantiating Document (new Document(...)) but the Utopia\Database\Document
class is not imported; add a top-level import "use Utopia\Database\Document;" to
the Update class file so PHP can resolve Document, and ensure both places where
updateDocument is called (the occurrences that create new Document with
key/value/secret/search — the call using
$dbForProject->updateDocument('variables', $variable->getId(), new
Document([...])) and the similar second update call) continue to use the
imported Document class.
In `@src/Appwrite/Platform/Workers/Migrations.php`:
- Around line 574-578: The code is directly calling
$this->dbForProject->updateDocument(...) to append the oversize-export error,
bypassing updateMigrationDocument() so Realtime events and the in-memory
$migration are not updated; change the flow to append the new error to
$migration->getAttribute('errors', []) and then call
updateMigrationDocument(...) (the existing method that handles persisting
changes, broadcasting Realtime updates and updating the in-memory migration)
instead of calling dbForProject->updateDocument directly, ensuring the migration
state and clients receive the error update.
---
Nitpick comments:
In `@AGENTS.md`:
- Around line 54-78: Add an exception to the guidance that sparse Document
updates are not appropriate for atomic/read-modify-write operations (e.g.,
counters or append-style array updates) where concurrency-safety is required;
reference updateDocument(), Document, array_merge(), and getArrayCopy() and
state that in these cases callers should use DB-provided atomic operations or a
full-document update/locking strategy instead of a sparse Document to ensure
correctness under concurrent requests.
In
`@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php`:
- Around line 150-154: The code calls DateTime::now() twice causing potential
minor timestamp drift; capture the current timestamp once into a variable (e.g.
$now) and reuse it for both $session->setAttribute('mfaUpdatedAt', ...) and the
new Document(['mfaUpdatedAt' => ...]) used in $dbForProject->updateDocument to
ensure the session attribute and stored document share the identical timestamp;
update references to DateTime::now() in the Account MFA update block accordingly
(affecting $session, setAttribute, and the Document passed to updateDocument).
In `@src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php`:
- Around line 103-108: The variable update duplicates the fields in
setAttribute(...) and in the new Document([...]) passed to
$dbForProject->updateDocument; create a single associative array (e.g.
$variablePatch) containing 'key', 'value', 'secret', and 'search' (constructed
the same way as currently done with $variableId, $function->getId(), $key,
'function'), then pass that array to new Document($variablePatch) for the DB
update and call $variable->setAttribute(...) with the same $variablePatch values
so both in-memory and persisted representations come from the same source; apply
the same pattern for the schedule update block (use $schedulePatch) to avoid
duplication and keep them in sync.
In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php`:
- Around line 67-71: Remove the redundant mutations: delete the
$team->setAttribute('name', $name) and $team->setAttribute('search', ...) calls
since they are not used before $team is reassigned by
dbForProject->updateDocument; keep the update call that creates the new sparse
Document (new Document(['name' => $name, 'search' => implode(' ', [$teamId,
$name])])) and assign its result back to $team, ensuring only the updateDocument
invocation and Document construction remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9ebe00f2-a2bc-4a1a-9f49-597b2258229d
📒 Files selected for processing (59)
AGENTS.mdCLAUDE.mdapp/controllers/api/account.phpapp/controllers/api/messaging.phpapp/controllers/api/users.phpapp/controllers/general.phpapp/controllers/shared/api.phpapp/init/resources.phpapp/realtime.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.phpsrc/Appwrite/Platform/Modules/Compute/Base.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Workers/Databases.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Functions/Workers/Builds.phpsrc/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.phpsrc/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.phpsrc/Appwrite/Platform/Tasks/Interval.phpsrc/Appwrite/Platform/Workers/Certificates.phpsrc/Appwrite/Platform/Workers/Messaging.phpsrc/Appwrite/Platform/Workers/Migrations.phpsrc/Appwrite/Platform/Workers/Webhooks.php
src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
Show resolved
Hide resolved
4e84a2e to
f2ec1b1
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)
93-96:⚠️ Potential issue | 🔴 CriticalAdd the missing
Utopia\Database\Documentimport.Line 93 instantiates
new Document([...]), but this file never importsUtopia\Database\Document. In PHP that resolves to the current namespace, so deleting a function with a schedule will fatal at runtime instead of updating the schedule.🐛 Proposed fix
use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around lines 93 - 96, The code instantiates new Document(...) within Delete.php (see the call to $dbForPlatform->updateDocument and the new Document([...]) expression) but lacks the Utopia\Database\Document import; add a "use Utopia\Database\Document;" import at the top of the file so new Document resolves to the correct class and the schedule update won’t fatal at runtime.src/Appwrite/Platform/Workers/Migrations.php (1)
574-578:⚠️ Potential issue | 🟠 MajorUse
updateMigrationDocument()in the export-limit failure path.Line 576 writes
errorsstraight to the database, which skips the Realtime update and leaves the in-memory$migrationstale. Clients can miss this failure after already seeing the earliercompletedevent.🔧 Proposed fix
$errors = $migration->getAttribute('errors', []); $errors[] = json_encode(['code' => 0, 'message' => $message]); - $this->dbForProject->updateDocument('migrations', $migration->getId(), new Document([ - 'errors' => $errors, - ])); + $migration->setAttribute('errors', $errors); + $this->updateMigrationDocument($migration, $project, $queueForRealtime);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Workers/Migrations.php` around lines 574 - 578, The migration failure branch currently writes errors directly with $this->dbForProject->updateDocument('migrations', $migration->getId(), ...) which bypasses the centralized update path and leaves the in-memory $migration and Realtime listeners stale; replace that direct update with a call to the existing updateMigrationDocument(...) helper (passing the migration id and the updated 'errors' payload or merging via getAttribute('errors', []) then adding the new JSON error) so the document is updated through the proper flow and triggers Realtime/in-memory sync.src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php (1)
122-125:⚠️ Potential issue | 🔴 CriticalAdd the missing
Documentimport.Line 122 instantiates
new Document(...), butUtopia\Database\Documentis not imported in this file, so this branch will fail at runtime.🔧 Proposed fix
use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Query;#!/bin/bash set -euo pipefail file="src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php" echo "=== Imports ===" sed -n '1,20p' "$file" echo echo "=== Document instantiations ===" rg -n '\bnew Document\s*\(' "$file"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php` around lines 122 - 125, The file instantiates new Document in the $authorization->skip callback passed to $dbForPlatform->updateDocument but never imports Utopia\Database\Document; add a use statement importing Utopia\Database\Document at the top of the file so the new Document([...]) call resolves. Locate the instantiation in the Delete class where $authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([...]))); and add the corresponding use Utopia\Database\Document; import alongside the other imports.src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php (1)
122-127:⚠️ Potential issue | 🔴 CriticalMissing
Documentimport will cause fatal error.The code uses
new Document([...])butUtopia\Database\Documentis not imported. This will cause a runtime error.🐛 Proposed fix: Add missing import
Add to the imports section (around line 12):
use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php` around lines 122 - 127, The file is instantiating Utopia\Database\Document via new Document(...) but the class isn't imported, causing a fatal error; add a use statement for Utopia\Database\Document in the imports block at the top of Create.php so the Document symbol resolves (the code paths using updateDocument in the Create class and the new Document([...]) call will then work).
🧹 Nitpick comments (6)
src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php (1)
81-84: RedundantsetAttributecall.Line 82 modifies
$project, but line 84 immediately reassigns$projectto the return value ofupdateDocument(). Since the sparseDocumentalready contains thelabelsfield, thesetAttribute()call has no effect on the final outcome and can be removed.♻️ Proposed fix
$labels = (array) \array_values(\array_unique($labels)); - $project->setAttribute('labels', $labels); $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php` around lines 81 - 84, The call to $project->setAttribute('labels', $labels) is redundant because $project is immediately overwritten by the result of $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels])); remove the $project->setAttribute('labels', $labels) line to avoid dead mutation and rely on updateDocument(...) with the sparse Document(['labels' => $labels]) to persist the change.src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php (2)
89-108: Remove deadsetAttributecalls from loops.Lines 90, 98, and 106 modify local variables that are never used afterward—the sparse Documents passed to
updateDocument()already contain the updated values. Remove these dead lines.♻️ Proposed fix
$installations = $dbForPlatform->find('installations', [ Query::equal('projectInternalId', [$project->getSequence()]), ]); foreach ($installations as $installation) { - $installation->setAttribute('$permissions', $permissions); $dbForPlatform->updateDocument('installations', $installation->getId(), new Document(['$permissions' => $permissions])); } $repositories = $dbForPlatform->find('repositories', [ Query::equal('projectInternalId', [$project->getSequence()]), ]); foreach ($repositories as $repository) { - $repository->setAttribute('$permissions', $permissions); $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['$permissions' => $permissions])); } $vcsComments = $dbForPlatform->find('vcsComments', [ Query::equal('projectInternalId', [$project->getSequence()]), ]); foreach ($vcsComments as $vcsComment) { - $vcsComment->setAttribute('$permissions', $permissions); $dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), new Document(['$permissions' => $permissions])); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php` around lines 89 - 108, Remove the dead local mutations by deleting the calls to setAttribute(...) inside the three loops that iterate $installations, $repositories, and $vcsComments; the calls to updateDocument('installations'|'repositories'|'vcsComments', ..., new Document(['$permissions' => $permissions])) already persist the change so remove the unused $installation->setAttribute('$permissions', $permissions), $repository->setAttribute('$permissions', $permissions), and $vcsComment->setAttribute('$permissions', $permissions) lines to avoid needless local updates.
76-84: Remove dead code from old update pattern.The
setAttributecalls on lines 76-79 are now dead code since$projectis immediately overwritten by the return value ofupdateDocument(). These lines should be removed for clarity.♻️ Proposed fix
$permissions = $this->getPermissions($teamId, $projectId); - $project - ->setAttribute('teamId', $teamId) - ->setAttribute('teamInternalId', $team->getSequence()) - ->setAttribute('$permissions', $permissions); $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'teamId' => $teamId, 'teamInternalId' => $team->getSequence(), '$permissions' => $permissions, ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php` around lines 76 - 84, The three setAttribute calls on $project (setAttribute('teamId', ...), setAttribute('teamInternalId', ...), setAttribute('$permissions', ...)) are dead because $project is immediately reassigned by $dbForPlatform->updateDocument(...); remove these redundant setAttribute lines and rely on the updateDocument call that constructs the new Document({ 'teamId' => ..., 'teamInternalId' => ..., '$permissions' => ... }) to apply the changes.src/Appwrite/Platform/Tasks/Interval.php (1)
162-164: Remove the redundant$executionmutations here.Line 164 already writes the sparse payload, so the
setAttribute()calls on Lines 162-163 no longer affect persistence and just keep part of the old full-document work around.♻️ Proposed cleanup
foreach ($staleExecutions as $execution) { - $execution->setAttribute('status', 'failed'); - $execution->setAttribute('errors', 'Execution timed out'); - $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])); + $dbForProject->updateDocument( + 'executions', + $execution->getId(), + new Document([ + 'status' => 'failed', + 'errors' => 'Execution timed out', + ]) + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 162 - 164, Remove the redundant in-memory mutations on $execution: delete the two calls to $execution->setAttribute('status', 'failed') and $execution->setAttribute('errors', 'Execution timed out') and keep the single sparse-patch persistence call $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])); so only the updateDocument persists the failure state.src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php (1)
100-103: Remove the leftover full-document mutation.Line 101 is redundant now that Line 103 persists
providerPullRequestIdsvia a sparseDocumentand returns the updated repository. Dropping the intermediatesetAttribute()keeps this path fully aligned with the optimization pattern introduced by the PR.♻️ Suggested cleanup
$providerPullRequestIds = \array_unique(\array_merge($repository->getAttribute('providerPullRequestIds', []), [$providerPullRequestId])); -$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds); - $repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php` around lines 100 - 103, The intermediate full-document mutation using $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds) is redundant because $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds]))) already persists and returns the updated repository; remove the $repository = $repository->setAttribute(...) line and keep only the array_unique merge into $providerPullRequestIds and the authorization->skip updateDocument call (preserving $providerPullRequestIds, $repository->getId(), Document).src/Appwrite/Platform/Workers/Webhooks.php (1)
190-193: Redundant local modification on line 190.The
$webhook->setAttribute('attempts', 0)call is now unnecessary since the sparse Document already contains the hardcoded value'attempts' => 0, and the local$webhookobject'sattemptsattribute is never read after this point.♻️ Suggested cleanup
} else { - $webhook->setAttribute('attempts', 0); // Reset attempts on success $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document([ 'attempts' => 0, ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Workers/Webhooks.php` around lines 190 - 193, Remove the redundant local modification: delete the call to $webhook->setAttribute('attempts', 0) since you already persist attempts => 0 via $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document(['attempts' => 0])), and the $webhook object's attempts attribute is not used after this point; leave the updateDocument call intact and ensure no subsequent code expects the local $webhook to reflect the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/controllers/api/messaging.php`:
- Around line 2695-2698: The PATCH currently always writes both 'name' and
'subscribe' by creating a new Document from the existing $topic, risking stale
overwrites; instead build a sparse payload using only request-touched params
(e.g. check the incoming request body or param getters for 'name' and
'subscribe'), add each non-null/isset field to an $updateFields array, and call
$dbForProject->updateDocument('topics', $topicId, new Document($updateFields));
ensure you reference the same $topicId and $dbForProject/updateDocument call but
do not read values from the mutated $topic when constructing the Document.
In `@app/controllers/api/users.php`:
- Around line 1495-1498: The patch updates only the DB so the in-memory embedded
target ($oldTarget inside $user->targets) remains stale; before returning $user,
update the embedded Document or replace it in $user->targets to reflect the new
identifier (e.g., call $oldTarget->set('identifier', $email) or assign a new
Document with the updated identifier into the array) immediately after calling
$dbForProject->updateDocument('targets', $oldTarget->getId(), ...); apply the
same in-memory sync where the other target-update block occurs (the similar
update around the other mentioned location).
---
Duplicate comments:
In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The file is instantiating Utopia\Database\Document via
new Document(...) but the class isn't imported, causing a fatal error; add a use
statement for Utopia\Database\Document in the imports block at the top of
Create.php so the Document symbol resolves (the code paths using updateDocument
in the Create class and the new Document([...]) call will then work).
In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php`:
- Around line 122-125: The file instantiates new Document in the
$authorization->skip callback passed to $dbForPlatform->updateDocument but never
imports Utopia\Database\Document; add a use statement importing
Utopia\Database\Document at the top of the file so the new Document([...]) call
resolves. Locate the instantiation in the Delete class where
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules',
$schedule->getId(), new Document([...]))); and add the corresponding use
Utopia\Database\Document; import alongside the other imports.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) within Delete.php
(see the call to $dbForPlatform->updateDocument and the new Document([...])
expression) but lacks the Utopia\Database\Document import; add a "use
Utopia\Database\Document;" import at the top of the file so new Document
resolves to the correct class and the schedule update won’t fatal at runtime.
In `@src/Appwrite/Platform/Workers/Migrations.php`:
- Around line 574-578: The migration failure branch currently writes errors
directly with $this->dbForProject->updateDocument('migrations',
$migration->getId(), ...) which bypasses the centralized update path and leaves
the in-memory $migration and Realtime listeners stale; replace that direct
update with a call to the existing updateMigrationDocument(...) helper (passing
the migration id and the updated 'errors' payload or merging via
getAttribute('errors', []) then adding the new JSON error) so the document is
updated through the proper flow and triggers Realtime/in-memory sync.
---
Nitpick comments:
In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`:
- Around line 81-84: The call to $project->setAttribute('labels', $labels) is
redundant because $project is immediately overwritten by the result of
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])); remove the $project->setAttribute('labels',
$labels) line to avoid dead mutation and rely on updateDocument(...) with the
sparse Document(['labels' => $labels]) to persist the change.
In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php`:
- Around line 89-108: Remove the dead local mutations by deleting the calls to
setAttribute(...) inside the three loops that iterate $installations,
$repositories, and $vcsComments; the calls to
updateDocument('installations'|'repositories'|'vcsComments', ..., new
Document(['$permissions' => $permissions])) already persist the change so remove
the unused $installation->setAttribute('$permissions', $permissions),
$repository->setAttribute('$permissions', $permissions), and
$vcsComment->setAttribute('$permissions', $permissions) lines to avoid needless
local updates.
- Around line 76-84: The three setAttribute calls on $project
(setAttribute('teamId', ...), setAttribute('teamInternalId', ...),
setAttribute('$permissions', ...)) are dead because $project is immediately
reassigned by $dbForPlatform->updateDocument(...); remove these redundant
setAttribute lines and rely on the updateDocument call that constructs the new
Document({ 'teamId' => ..., 'teamInternalId' => ..., '$permissions' => ... }) to
apply the changes.
In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php`:
- Around line 100-103: The intermediate full-document mutation using
$repository->setAttribute('providerPullRequestIds', $providerPullRequestIds) is
redundant because $authorization->skip(fn () =>
$dbForPlatform->updateDocument('repositories', $repository->getId(), new
Document(['providerPullRequestIds' => $providerPullRequestIds]))) already
persists and returns the updated repository; remove the $repository =
$repository->setAttribute(...) line and keep only the array_unique merge into
$providerPullRequestIds and the authorization->skip updateDocument call
(preserving $providerPullRequestIds, $repository->getId(), Document).
In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 162-164: Remove the redundant in-memory mutations on $execution:
delete the two calls to $execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and keep the single
sparse-patch persistence call $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); so only the updateDocument persists the failure state.
In `@src/Appwrite/Platform/Workers/Webhooks.php`:
- Around line 190-193: Remove the redundant local modification: delete the call
to $webhook->setAttribute('attempts', 0) since you already persist attempts => 0
via $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new
Document(['attempts' => 0])), and the $webhook object's attempts attribute is
not used after this point; leave the updateDocument call intact and ensure no
subsequent code expects the local $webhook to reflect the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0c692097-42c8-460b-8391-336704001820
📒 Files selected for processing (59)
AGENTS.mdCLAUDE.mdapp/controllers/api/account.phpapp/controllers/api/messaging.phpapp/controllers/api/users.phpapp/controllers/general.phpapp/controllers/shared/api.phpapp/init/resources.phpapp/realtime.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.phpsrc/Appwrite/Platform/Modules/Compute/Base.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Workers/Databases.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Functions/Workers/Builds.phpsrc/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.phpsrc/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.phpsrc/Appwrite/Platform/Tasks/Interval.phpsrc/Appwrite/Platform/Workers/Certificates.phpsrc/Appwrite/Platform/Workers/Messaging.phpsrc/Appwrite/Platform/Workers/Migrations.phpsrc/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (22)
- src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
- AGENTS.md
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
- app/controllers/general.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
- src/Appwrite/Platform/Workers/Messaging.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
- src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
- app/realtime.php
- CLAUDE.md
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
- src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
f2ec1b1 to
99dda0e
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (4)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)
93-96:⚠️ Potential issue | 🔴 CriticalImport
Utopia\Database\Documentbefore usingnew Document(...).Line 93 introduces
new Document([...]), but this file does not importUtopia\Database\Document. In this namespace, PHP will resolveDocumenttoAppwrite\Platform\Modules\Functions\Http\Functions\Document, which will fail at runtime.🐛 Proposed fix
use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID;#!/bin/bash set -euo pipefail file="src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php" echo "== Imports ==" sed -n '1,20p' "$file" echo echo "== Document instantiation ==" rg -n 'new Document\(' "$file" echo echo "== Document import ==" rg -n '^use Utopia\\Database\\Document;' "$file" || true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around lines 93 - 96, The code instantiates new Document(...) without importing it, causing PHP to resolve it to the current namespace; fix by importing Utopia\Database\Document at the top of the file (add use Utopia\Database\Document;) or alternatively fully-qualify the instantiation as new \Utopia\Database\Document([...]) where $authorization->skip(...) calls $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([...])) so the correct class is used.src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php (1)
122-127:⚠️ Potential issue | 🔴 CriticalImport
Utopia\Database\Documentbefore this sparse update.Line 122 instantiates
new Document(...), but this file does not import the databaseDocumentclass. In this namespace, that resolves to a non-existent class and will fatal the duplicate-deployment flow.🐛 Proposed fix
use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID;#!/bin/bash set -euo pipefail file="src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php" echo "=== Imports ===" sed -n '1,18p' "$file" echo echo "=== Document instantiations ===" rg -n 'new Document' "$file"Expected result:
new Document(...)is present, butuse Utopia\Database\Document;is missing from the imports.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php` around lines 122 - 127, The code instantiates new Document(...) in the Duplicate deployment flow (see the new Document(...) call in the Create class), but the Utopia\Database\Document class is not imported; add the import statement "use Utopia\Database\Document;" to the top of the file's imports so the new Document(...) resolves to the correct database Document class and the duplicate-deployment flow no longer fatals.app/controllers/api/account.php (2)
4521-4525:⚠️ Potential issue | 🟠 MajorDon't write
expiredback unless this request changes it.
createPushTarget()does not initializeexpired, so this sparse update can still persistnullwhen the request only refreshes the identifier/name.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 4521 - 4525, The update is overwriting the target.expired field even when the request didn't supply it (createPushTarget leaves expired unset), so change the code around updateDocument('targets', ...) to build the Document payload conditionally: include 'expired' only if the incoming request explicitly provides/changes the expired value (e.g. check the request for an 'expired' parameter or compare the requested expired value to $target->getAttribute('expired') before adding it), otherwise omit 'expired' from the Document so null is not persisted unintentionally.
291-294:⚠️ Potential issue | 🟠 MajorOnly persist the verification flag that actually changed.
This still writes both verification fields back. In flows where the other attribute was never initialized, the sparse update can write
nulland either fail validation or alter the stored shape unexpectedly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 291 - 294, Only send the verification field(s) that actually changed to updateDocument: before calling $dbForProject->updateDocument('users', $user->getId(), new Document(...)) build an $updates array and conditionally add 'emailVerification' and/or 'phoneVerification' only when their value changed on $user (compare current value to the original/previous value or use the model's dirty/changed API), then call updateDocument with new Document($updates) so you never write an unmodified field back as null or into the stored shape.
🧹 Nitpick comments (3)
src/Appwrite/Platform/Tasks/Interval.php (1)
161-164: Remove the redundantsetAttribute()calls.
updateDocument()now receives a sparseDocument, so Lines 162-163 mutate the full$executionwithout affecting the write payload. Dropping those mutations keeps this path aligned with the optimization this PR is introducing.♻️ Proposed cleanup
foreach ($staleExecutions as $execution) { - $execution->setAttribute('status', 'failed'); - $execution->setAttribute('errors', 'Execution timed out'); - $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])); + $dbForProject->updateDocument( + 'executions', + $execution->getId(), + new Document([ + 'status' => 'failed', + 'errors' => 'Execution timed out', + ]) + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 161 - 164, Remove the redundant mutations to the in-memory execution before persisting: in the foreach over $staleExecutions in Interval.php, delete the two calls $execution->setAttribute('status', 'failed') and $execution->setAttribute('errors', 'Execution timed out') and rely solely on the sparse Document passed to $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])); this keeps the optimization consistent and avoids mutating the full $execution object.src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)
280-289: RedundantsetAttributecalls before sparse document update.Lines 280-281 set attributes on
$deployment, but the variable is immediately reassigned to theupdateDocumentresult (line 282). ThesesetAttributecalls have no effect since the sparse Document already contains the same values. Consider removing them for clarity:- $deployment->setAttribute('buildStartedAt', $startTime); - $deployment->setAttribute('status', 'processing'); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([ 'buildStartedAt' => $startTime, 'status' => 'processing', ]));This pattern repeats throughout the file (lines 368-376, 485-496, 540-548, 562-565, 881-898 with 929-936, 948-955, 1193-1203). The sparse document approach is correct, but the preceding
setAttributecalls are now dead code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php` around lines 280 - 289, Remove the redundant in-memory attribute assignments before sparse updates: any calls to $deployment->setAttribute(...) (e.g., the two calls before $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([...])) ) should be deleted because updateDocument returns a new Document and the setAttribute has no effect; keep the sparse Document payload passed to updateDocument (new Document([...])) and similarly remove preceding setAttribute calls in the other locations mentioned (the blocks around lines with $deployment updates and the related $resource->updateDocument calls) so only the sparse update is performed and the returned Document is used.app/controllers/api/users.php (1)
1791-1797: Consider building the sparse document with only actually changed fields.The current implementation always includes all five fields (
identifier,expired,providerId,providerInternalId,name) in the update, even when only a subset changed. Since the PR aims to optimize updates by passing only changed attributes, consider conditionally building the sparse document:♻️ Suggested refactor
- $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([ - 'identifier' => $target->getAttribute('identifier'), - 'expired' => $target->getAttribute('expired'), - 'providerId' => $target->getAttribute('providerId'), - 'providerInternalId' => $target->getAttribute('providerInternalId'), - 'name' => $target->getAttribute('name'), - ])); + $updatePayload = []; + if ($identifier) { + $updatePayload['identifier'] = $target->getAttribute('identifier'); + $updatePayload['expired'] = $target->getAttribute('expired'); + } + if ($providerId) { + $updatePayload['providerId'] = $target->getAttribute('providerId'); + $updatePayload['providerInternalId'] = $target->getAttribute('providerInternalId'); + } + if ($name) { + $updatePayload['name'] = $target->getAttribute('name'); + } + $target = $dbForProject->updateDocument('targets', $target->getId(), new Document($updatePayload));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1791 - 1797, The update currently always passes all five fields to updateDocument('targets', $target->getId(), new Document(...)) even when some didn't change; change this to build a sparse $payload array containing only attributes that actually changed (e.g. compare $target->getAttribute('identifier') etc. against the original values via a getOriginalAttribute/getOriginal* helper or a $target->getChangedAttributes() list), only add changed keys to $payload, and if $payload is non-empty call $dbForProject->updateDocument('targets', $target->getId(), new Document($payload)); if there are no changes skip the updateDocument call. Ensure you reference the existing symbols updateDocument, 'targets', $target->getId(), and getAttribute/getOriginalAttribute (or getChangedAttributes) when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php`:
- Line 154: The update call is writing DateTime::now() into the sessions
document which can differ from the timestamp already placed on the $session
object earlier; replace DateTime::now() with the existing timestamp value from
$session (e.g. $session->getAttribute('mfaUpdatedAt') or the property used
earlier) when calling $dbForProject->updateDocument('sessions',
$session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' =>
<reuse-session-timestamp>])); to ensure the persisted mfaUpdatedAt exactly
matches the session returned to the client.
In `@src/Appwrite/Platform/Modules/Databases/Workers/Databases.php`:
- Around line 322-329: The DB update writes status=>'stuck' but the in-memory
$attribute object isn't updated, so realtime notifications sent later use the
old state; after calling $dbForProject->updateDocument('attributes',
$attribute->getId(), ...) update the local $attribute instance (e.g.,
setAttribute('status','stuck') or equivalent method on the Attribute class) and
also ensure its 'error' field is updated to match the persisted values before
the finally block triggers realtime so listeners receive the aligned stuck/error
payload.
In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 929-936: The update currently always writes 'adapter' and
'fallbackFile' into the sparse Document (in the call to
$dbForProject->updateDocument and the new Document([...])) even though those
attributes are only set for site resources; change the update to build the
payload array first and only add 'adapter' and 'fallbackFile' when
$resource->getCollection() === 'sites' (i.e. conditionally push those keys into
the array before calling updateDocument with new Document($payload)), so that
function deployments don't receive unset adapter/fallbackFile fields.
In `@src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php`:
- Line 77: The call to $dbForPlatform->updateDocument('devKeys', $key->getId(),
new Document(['name' => $name, 'expire' => $expire])) discards the DB-returned
Document (which contains DB-managed fields like updatedAt); assign the result
back to $key (e.g. $key = $dbForPlatform->updateDocument(...)) and then
serialize/use $key for the response so timestamps are current — mirror the
pattern used in Projects/Update.php.
In `@src/Appwrite/Platform/Workers/Webhooks.php`:
- Around line 176-179: The update is always writing the 'enabled' field which
can incorrectly flip admin changes; change the updateDocument call so it only
includes 'enabled' in the Document payload when this failure branch actually
pauses the webhook (i.e., only add the 'enabled' key when you intend to set it
to false), keep 'logs' always, and build the payload dynamically before calling
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document(...))
so that when not pausing the 'enabled' attribute is omitted entirely.
---
Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The update is overwriting the target.expired field even
when the request didn't supply it (createPushTarget leaves expired unset), so
change the code around updateDocument('targets', ...) to build the Document
payload conditionally: include 'expired' only if the incoming request explicitly
provides/changes the expired value (e.g. check the request for an 'expired'
parameter or compare the requested expired value to
$target->getAttribute('expired') before adding it), otherwise omit 'expired'
from the Document so null is not persisted unintentionally.
- Around line 291-294: Only send the verification field(s) that actually changed
to updateDocument: before calling $dbForProject->updateDocument('users',
$user->getId(), new Document(...)) build an $updates array and conditionally add
'emailVerification' and/or 'phoneVerification' only when their value changed on
$user (compare current value to the original/previous value or use the model's
dirty/changed API), then call updateDocument with new Document($updates) so you
never write an unmodified field back as null or into the stored shape.
In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The code instantiates new Document(...) in the Duplicate
deployment flow (see the new Document(...) call in the Create class), but the
Utopia\Database\Document class is not imported; add the import statement "use
Utopia\Database\Document;" to the top of the file's imports so the new
Document(...) resolves to the correct database Document class and the
duplicate-deployment flow no longer fatals.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) without importing
it, causing PHP to resolve it to the current namespace; fix by importing
Utopia\Database\Document at the top of the file (add use
Utopia\Database\Document;) or alternatively fully-qualify the instantiation as
new \Utopia\Database\Document([...]) where $authorization->skip(...) calls
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...])) so the correct class is used.
---
Nitpick comments:
In `@app/controllers/api/users.php`:
- Around line 1791-1797: The update currently always passes all five fields to
updateDocument('targets', $target->getId(), new Document(...)) even when some
didn't change; change this to build a sparse $payload array containing only
attributes that actually changed (e.g. compare
$target->getAttribute('identifier') etc. against the original values via a
getOriginalAttribute/getOriginal* helper or a $target->getChangedAttributes()
list), only add changed keys to $payload, and if $payload is non-empty call
$dbForProject->updateDocument('targets', $target->getId(), new
Document($payload)); if there are no changes skip the updateDocument call.
Ensure you reference the existing symbols updateDocument, 'targets',
$target->getId(), and getAttribute/getOriginalAttribute (or
getChangedAttributes) when making the change.
In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 280-289: Remove the redundant in-memory attribute assignments
before sparse updates: any calls to $deployment->setAttribute(...) (e.g., the
two calls before $dbForProject->updateDocument('deployments',
$deployment->getId(), new Document([...])) ) should be deleted because
updateDocument returns a new Document and the setAttribute has no effect; keep
the sparse Document payload passed to updateDocument (new Document([...])) and
similarly remove preceding setAttribute calls in the other locations mentioned
(the blocks around lines with $deployment updates and the related
$resource->updateDocument calls) so only the sparse update is performed and the
returned Document is used.
In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 161-164: Remove the redundant mutations to the in-memory execution
before persisting: in the foreach over $staleExecutions in Interval.php, delete
the two calls $execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and rely solely on the
sparse Document passed to $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); this keeps the optimization consistent and avoids mutating the
full $execution object.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ef6296b5-9617-4bf1-aa5c-035ae4a33d91
📒 Files selected for processing (59)
AGENTS.mdCLAUDE.mdapp/controllers/api/account.phpapp/controllers/api/messaging.phpapp/controllers/api/users.phpapp/controllers/general.phpapp/controllers/shared/api.phpapp/init/resources.phpapp/realtime.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.phpsrc/Appwrite/Platform/Modules/Compute/Base.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Workers/Databases.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Functions/Workers/Builds.phpsrc/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.phpsrc/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.phpsrc/Appwrite/Platform/Tasks/Interval.phpsrc/Appwrite/Platform/Workers/Certificates.phpsrc/Appwrite/Platform/Workers/Messaging.phpsrc/Appwrite/Platform/Workers/Migrations.phpsrc/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (27)
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
- src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
- src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
- app/realtime.php
- app/controllers/general.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
- app/controllers/api/messaging.php
- AGENTS.md
- src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
- app/controllers/shared/api.php
- src/Appwrite/Platform/Workers/Certificates.php
- src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
- src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
- src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
- src/Appwrite/Platform/Modules/Compute/Base.php
- src/Appwrite/Platform/Workers/Messaging.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
- src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
- src/Appwrite/Platform/Workers/Migrations.php
- CLAUDE.md
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
Outdated
Show resolved
Hide resolved
99dda0e to
1fefbaa
Compare
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 (1)
app/controllers/api/users.php (1)
1342-1353:⚠️ Potential issue | 🟠 MajorStop the empty-password branch from falling through.
After Line 1352 sends the response, execution still continues into the normal hashing path, so the cleared password is immediately replaced with an Argon2 hash of
''.Proposed fix
if (\strlen($password) === 0) { $user ->setAttribute('password', '') ->setAttribute('passwordUpdate', DateTime::now()); $user = $dbForProject->updateDocument('users', $user->getId(), new Document([ 'password' => $user->getAttribute('password'), 'passwordUpdate' => $user->getAttribute('passwordUpdate'), ])); $queueForEvents->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); + return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1342 - 1353, The empty-password branch (where strlen($password) === 0) sends a response but does not stop execution, so after updating the user and calling $response->dynamic($user, Response::MODEL_USER) the code continues into the normal hashing path and re-hashes the empty password; fix by exiting the handler immediately after sending the response—e.g., add an explicit return (or otherwise short-circuit) right after $response->dynamic($user, Response::MODEL_USER) so the subsequent password hashing/update logic does not run for the empty-password case.
♻️ Duplicate comments (5)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php (1)
286-291:⚠️ Potential issue | 🔴 CriticalDon't overwrite
$functionwith the sparse update result.
updateDocument()returns aDocument, and the adapter contract plus the MariaDB implementation show it returns the document passed in rather than a reloaded merged row. Reassigning here truncates$functionto just these four fields, so later reads like Line 314 and the final payload at Line 429 can lose attributes unexpectedly. (raw.githubusercontent.com)Suggested fix
- $function = $dbForProject->updateDocument('functions', $function->getId(), new Document([ + $dbForProject->updateDocument('functions', $function->getId(), new Document([ 'scheduleId' => $function->getAttribute('scheduleId'), 'scheduleInternalId' => $function->getAttribute('scheduleInternalId'), 'repositoryId' => $function->getAttribute('repositoryId'), 'repositoryInternalId' => $function->getAttribute('repositoryInternalId'), ]));In utopia-php/database, does Adapter::updateDocument return the passed Document, and does the MariaDB adapter return that sparse input document instead of refetching the merged row?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php` around lines 286 - 291, The current code reassigns $function with the sparse Document returned by $dbForProject->updateDocument(...), which truncates attributes; instead do not overwrite $function—store the update result in a new variable (e.g. $updatedDocument) or, better, after calling updateDocument, reload the full row using the DB read method (e.g. getDocument/readDocument by passing $function->getId()) and use that fresh full Document for later reads; ensure updateDocument is only used for the write and subsequent code (lines referencing $function) uses the original $function or the freshly fetched full Document.src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)
93-96:⚠️ Potential issue | 🔴 CriticalImport
Utopia\Database\Documentbefore instantiating it.Line 93 uses
new Document([...]), but this file never importsUtopia\Database\Document, so PHP will resolveDocumentto the current namespace and this delete path will fatal at runtime.🐛 Proposed fix
use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization;Run this read-only check to confirm the missing import. Expect the
new Document(match to exist and theusematch to be absent:#!/bin/bash file="src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php" sed -n '1,25p' "$file" rg -n "new Document\\(" "$file" rg -n "^use Utopia\\\\Database\\\\Document;" "$file"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around lines 93 - 96, The code instantiates new Document(...) in the Delete flow ($authorization->skip → $dbForPlatform->updateDocument) but doesn't import Utopia\Database\Document, causing a fatal at runtime; fix by adding the missing use statement for Utopia\Database\Document at the top of the file (so the new Document([...]) in the Delete.php function resolves to the correct class).app/controllers/api/account.php (2)
4521-4525:⚠️ Potential issue | 🟠 MajorDon't write
expiredunless this request sets it.
createPushTarget()in this file doesn't initializeexpired, so this sparse update can still sendexpired => nullwhenever the$identifierbranch is skipped.💡 Proposed fix
- $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([ - 'identifier' => $target->getAttribute('identifier'), - 'expired' => $target->getAttribute('expired'), - 'name' => $target->getAttribute('name'), - ])); + $updates = new Document([ + 'identifier' => $target->getAttribute('identifier'), + 'name' => $target->getAttribute('name'), + ]); + + if ($identifier) { + $updates->setAttribute('expired', false); + } + + $target = $dbForProject->updateDocument('targets', $target->getId(), $updates);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 4521 - 4525, The update currently always writes 'expired' (possibly null) in the sparse update called via $dbForProject->updateDocument('targets', ...), which can overwrite an existing value when createPushTarget() doesn't set it; change the payload construction so 'expired' is only included when the incoming request actually sets it (e.g., check the request param or verify $target->hasAttribute('expired') / $target->getAttribute('expired') !== null before adding 'expired' to the Document), leaving the field out of the Document entirely when not provided.
291-294:⚠️ Potential issue | 🟠 MajorOnly persist the verification flag that actually changed.
Lines 291-294 always emit both verification fields. Several user-creation paths in this file never initialize
phoneVerification, so this sparse update can writephoneVerification => nullfor email/magic-url logins.💡 Proposed fix
try { - $dbForProject->updateDocument('users', $user->getId(), new Document([ - 'emailVerification' => $user->getAttribute('emailVerification'), - 'phoneVerification' => $user->getAttribute('phoneVerification'), - ])); + $updates = new Document([]); + + if (\in_array($verifiedToken->getAttribute('type'), [TOKEN_TYPE_MAGIC_URL, TOKEN_TYPE_EMAIL], true)) { + $updates->setAttribute('emailVerification', true); + } + + if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) { + $updates->setAttribute('phoneVerification', true); + } + + if (!$updates->isEmpty()) { + $dbForProject->updateDocument('users', $user->getId(), $updates); + } } catch (\Throwable $th) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 291 - 294, The update currently always writes both 'emailVerification' and 'phoneVerification' which can overwrite phoneVerification with null; change the $dbForProject->updateDocument call to build a sparse $data array only including keys that were actually set/changed (e.g. check $user->hasAttribute or use !== null on $user->getAttribute('phoneVerification') and same for email), then call $dbForProject->updateDocument('users', $user->getId(), new Document($data)) only when $data is not empty so you never persist an unset phoneVerification; reference the existing updateDocument call, the 'users' collection, and the $user->getAttribute('phoneVerification') / 'emailVerification' attributes when implementing this conditional assembly.app/controllers/api/users.php (1)
1495-1500:⚠️ Potential issue | 🟠 MajorKeep the embedded target list in sync before returning
$user.These sparse target updates only change storage.
$oldTargetinside$user->targetsstill carries the previous identifier, and the delete branches never remove it, so the response can return stale email/phone targets even when the patch succeeded.Proposed fix
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { if (\strlen($email) !== 0) { + $oldTarget->setAttribute('identifier', $email); $dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $email])); } else { $dbForProject->deleteDocument('targets', $oldTarget->getId()); + $user->setAttribute( + 'targets', + array_values(array_filter( + $user->getAttribute('targets', []), + fn (Document $target) => $target->getId() !== $oldTarget->getId() + )) + ); } }Mirror the same fix in the phone branch.
Also applies to: 1587-1592
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1495 - 1500, The response can return stale targets because after calling $dbForProject->updateDocument(...) or ->deleteDocument(...) you do not update the in-memory $user->targets collection; locate the branches handling $oldTarget (the one that checks $oldTarget instanceof Document and strlen($email) !== 0) and mirror the storage changes into $user->targets: when updating, find the matching target object in $user->targets (by id from $oldTarget->getId()) and update its identifier to $email; when deleting, remove that target object from $user->targets. Apply the same sync logic to the phone branch that uses the phone variable so both identifier updates and deletions are reflected in $user->targets before returning $user.
🧹 Nitpick comments (4)
src/Appwrite/Platform/Tasks/Interval.php (1)
161-164: Remove the redundant full-document mutations.Lines 162-163 still mutate
$execution, but Line 164 persists a separate sparseDocument. That leaves extra work in this loop and duplicates the source of truth for the same update.♻️ Proposed cleanup
foreach ($staleExecutions as $execution) { - $execution->setAttribute('status', 'failed'); - $execution->setAttribute('errors', 'Execution timed out'); - $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])); + $dbForProject->updateDocument( + 'executions', + $execution->getId(), + new Document([ + 'status' => 'failed', + 'errors' => 'Execution timed out', + ]) + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 161 - 164, The loop currently updates the in-memory $execution with setAttribute('status','failed') and setAttribute('errors',...) and then separately calls $dbForProject->updateDocument('executions', $execution->getId(), new Document([...])) which duplicates work; remove the redundant in-memory mutations and rely on the single persistence call instead: delete the two lines calling $execution->setAttribute(...) and keep the $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out'])) so the status/errors update is applied only once.src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php (1)
81-84: Drop the redundant$project->setAttribute()call.Line 82 no longer participates in the write path: Line 84 persists a fresh sparse
Documentand immediately overwrites$projectwith the database result. Removing the in-memory mutation makes this sparse-update pattern easier to follow.♻️ Proposed cleanup
$labels = (array) \array_values(\array_unique($labels)); -$project->setAttribute('labels', $labels); - $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php` around lines 81 - 84, Remove the redundant in-memory mutation: delete the call to $project->setAttribute('labels', $labels) since the actual write uses $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels])) which returns the persisted $project; keep the deduping of $labels and the sparse Document update, then rely on the returned $project from updateDocument.src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php (1)
123-124: Remove redundantsetAttributecall.Line 123 modifies
$relatedAttribute, but the return value fromupdateDocument()on line 124 is not captured, and$relatedAttributeis never used after this block. ThesetAttributecall has no effect.♻️ Proposed cleanup
if ($relatedAttribute->getAttribute('status') === 'available') { - $relatedAttribute->setAttribute('status', 'deleting'); $dbForProject->updateDocument('attributes', $relatedAttribute->getId(), new Document(['status' => 'deleting'])); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php` around lines 123 - 124, The call to $relatedAttribute->setAttribute('status', 'deleting') is redundant because updateDocument('attributes', $relatedAttribute->getId(), new Document(['status' => 'deleting'])) performs the DB update and $relatedAttribute is not used afterwards; remove the $relatedAttribute->setAttribute(...) line OR, if you intended to update the in-memory object, replace it by assigning the result of dbForProject->updateDocument(...) back to $relatedAttribute (e.g., $relatedAttribute = $dbForProject->updateDocument(...)); update the code in the block that references $relatedAttribute, setAttribute, and updateDocument accordingly.src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php (1)
149-161: Build the sparse payload directly from the local values.This still mutates the full
$installationobject before the write, then immediately re-reads those fields into a sparseDocument. Passing$owner,$personal,$refreshToken,$accessToken, and$accessTokenExpirydirectly keeps the hot path leaner and avoids leaving the in-memory document ahead of storage ifupdateDocument()throws.♻️ Suggested cleanup
- $installation = $installation - ->setAttribute('organization', $owner) - ->setAttribute('personal', $personal) - ->setAttribute('personalRefreshToken', $refreshToken) - ->setAttribute('personalAccessToken', $accessToken) - ->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry); $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([ - 'organization' => $installation->getAttribute('organization'), - 'personal' => $installation->getAttribute('personal'), - 'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'), - 'personalAccessToken' => $installation->getAttribute('personalAccessToken'), - 'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'), + 'organization' => $owner, + 'personal' => $personal, + 'personalRefreshToken' => $refreshToken, + 'personalAccessToken' => $accessToken, + 'personalAccessTokenExpiry' => $accessTokenExpiry, ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php` around lines 149 - 161, The code mutates $installation with setAttribute calls before calling $dbForPlatform->updateDocument, which can leave the in-memory document changed if updateDocument() throws; instead build the sparse update payload directly from the local variables ($owner, $personal, $refreshToken, $accessToken, $accessTokenExpiry) and pass that to $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([...])) without calling $installation->setAttribute beforehand, then only update the in-memory $installation after a successful update if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/controllers/api/users.php`:
- Around line 1290-1292: The update currently sets attributes and writes a
sparse Document (e.g. in the name handler: $user->setAttribute('name', $name);
$dbForProject->updateDocument('users', $user->getId(), new Document(['name' =>
$user->getAttribute('name')]))) but it does not recompute the stored 'search'
field, so list queries still match old values; modify the name, email, and phone
update handlers to recompute the user's search token (same logic used when
building the full user Document) and include 'search' in the Document passed to
updateDocument('users', ...) so updates to name/email/phone also update the
stored search index (apply the same change in the other affected handlers
referenced around lines 1470-1489 and 1562-1581).
In `@src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php`:
- Around line 273-283: When auto-confirming the pending membership in the branch
that calls $dbForProject->updateDocument('memberships', $membership->getId(),
...) (the else branch that currently sets 'secret' and 'invited' and should also
set 'confirm' => true and 'joined'), also increment the teams.total counter just
like the privileged/app path does at the other branch (replicate the teams
update logic used there) so that confirmed-member counts remain accurate; locate
the membership update call using $membership and add a subsequent update to the
'teams' document to increment its 'total' field (or perform the same teams
update operation used in the other branch) when changing confirm from false to
true.
In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php`:
- Around line 100-103: The current read-check-append-write around
$providerPullRequestIds on $repository is not atomic and can lose concurrent PR
IDs; replace the manual merge and updateDocument call with an atomic DB update
that appends the PR id only if not present (or uses a set-union operator) so two
concurrent authorizations cannot clobber each other. Concretely, change the
logic around $providerPullRequestIds/$repository and the
$dbForPlatform->updateDocument('repositories', $repository->getId(), ...) call
to perform an atomic append (e.g., use the DB's array union/addToSet operator or
an atomic patch/update operation) or perform the update inside a DB
transaction/conditional update that checks absence of the PR id and retries on
conflict, so the append is done server-side and is safe for concurrent requests.
---
Outside diff comments:
In `@app/controllers/api/users.php`:
- Around line 1342-1353: The empty-password branch (where strlen($password) ===
0) sends a response but does not stop execution, so after updating the user and
calling $response->dynamic($user, Response::MODEL_USER) the code continues into
the normal hashing path and re-hashes the empty password; fix by exiting the
handler immediately after sending the response—e.g., add an explicit return (or
otherwise short-circuit) right after $response->dynamic($user,
Response::MODEL_USER) so the subsequent password hashing/update logic does not
run for the empty-password case.
---
Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The update currently always writes 'expired' (possibly
null) in the sparse update called via $dbForProject->updateDocument('targets',
...), which can overwrite an existing value when createPushTarget() doesn't set
it; change the payload construction so 'expired' is only included when the
incoming request actually sets it (e.g., check the request param or verify
$target->hasAttribute('expired') / $target->getAttribute('expired') !== null
before adding 'expired' to the Document), leaving the field out of the Document
entirely when not provided.
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' which can overwrite
phoneVerification with null; change the $dbForProject->updateDocument call to
build a sparse $data array only including keys that were actually set/changed
(e.g. check $user->hasAttribute or use !== null on
$user->getAttribute('phoneVerification') and same for email), then call
$dbForProject->updateDocument('users', $user->getId(), new Document($data)) only
when $data is not empty so you never persist an unset phoneVerification;
reference the existing updateDocument call, the 'users' collection, and the
$user->getAttribute('phoneVerification') / 'emailVerification' attributes when
implementing this conditional assembly.
In `@app/controllers/api/users.php`:
- Around line 1495-1500: The response can return stale targets because after
calling $dbForProject->updateDocument(...) or ->deleteDocument(...) you do not
update the in-memory $user->targets collection; locate the branches handling
$oldTarget (the one that checks $oldTarget instanceof Document and
strlen($email) !== 0) and mirror the storage changes into $user->targets: when
updating, find the matching target object in $user->targets (by id from
$oldTarget->getId()) and update its identifier to $email; when deleting, remove
that target object from $user->targets. Apply the same sync logic to the phone
branch that uses the phone variable so both identifier updates and deletions are
reflected in $user->targets before returning $user.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php`:
- Around line 286-291: The current code reassigns $function with the sparse
Document returned by $dbForProject->updateDocument(...), which truncates
attributes; instead do not overwrite $function—store the update result in a new
variable (e.g. $updatedDocument) or, better, after calling updateDocument,
reload the full row using the DB read method (e.g. getDocument/readDocument by
passing $function->getId()) and use that fresh full Document for later reads;
ensure updateDocument is only used for the write and subsequent code (lines
referencing $function) uses the original $function or the freshly fetched full
Document.
In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) in the Delete flow
($authorization->skip → $dbForPlatform->updateDocument) but doesn't import
Utopia\Database\Document, causing a fatal at runtime; fix by adding the missing
use statement for Utopia\Database\Document at the top of the file (so the new
Document([...]) in the Delete.php function resolves to the correct class).
---
Nitpick comments:
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php`:
- Around line 123-124: The call to $relatedAttribute->setAttribute('status',
'deleting') is redundant because updateDocument('attributes',
$relatedAttribute->getId(), new Document(['status' => 'deleting'])) performs the
DB update and $relatedAttribute is not used afterwards; remove the
$relatedAttribute->setAttribute(...) line OR, if you intended to update the
in-memory object, replace it by assigning the result of
dbForProject->updateDocument(...) back to $relatedAttribute (e.g.,
$relatedAttribute = $dbForProject->updateDocument(...)); update the code in the
block that references $relatedAttribute, setAttribute, and updateDocument
accordingly.
In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`:
- Around line 81-84: Remove the redundant in-memory mutation: delete the call to
$project->setAttribute('labels', $labels) since the actual write uses
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])) which returns the persisted $project; keep the
deduping of $labels and the sparse Document update, then rely on the returned
$project from updateDocument.
In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php`:
- Around line 149-161: The code mutates $installation with setAttribute calls
before calling $dbForPlatform->updateDocument, which can leave the in-memory
document changed if updateDocument() throws; instead build the sparse update
payload directly from the local variables ($owner, $personal, $refreshToken,
$accessToken, $accessTokenExpiry) and pass that to
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without calling $installation->setAttribute beforehand, then
only update the in-memory $installation after a successful update if needed.
In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 161-164: The loop currently updates the in-memory $execution with
setAttribute('status','failed') and setAttribute('errors',...) and then
separately calls $dbForProject->updateDocument('executions',
$execution->getId(), new Document([...])) which duplicates work; remove the
redundant in-memory mutations and rely on the single persistence call instead:
delete the two lines calling $execution->setAttribute(...) and keep the
$dbForProject->updateDocument('executions', $execution->getId(), new
Document(['status' => 'failed', 'errors' => 'Execution timed out'])) so the
status/errors update is applied only once.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6cb04582-bd41-405d-84c9-5fbd0b00d6bf
📒 Files selected for processing (59)
AGENTS.mdCLAUDE.mdapp/controllers/api/account.phpapp/controllers/api/messaging.phpapp/controllers/api/users.phpapp/controllers/general.phpapp/controllers/shared/api.phpapp/init/resources.phpapp/realtime.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.phpsrc/Appwrite/Platform/Modules/Compute/Base.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Workers/Databases.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Functions/Workers/Builds.phpsrc/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.phpsrc/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.phpsrc/Appwrite/Platform/Tasks/Interval.phpsrc/Appwrite/Platform/Workers/Certificates.phpsrc/Appwrite/Platform/Workers/Messaging.phpsrc/Appwrite/Platform/Workers/Migrations.phpsrc/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (30)
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
- src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
- src/Appwrite/Platform/Workers/Webhooks.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
- src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
- src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
- src/Appwrite/Platform/Workers/Messaging.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
- app/init/resources.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
- src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
- src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
- src/Appwrite/Platform/Workers/Migrations.php
- app/controllers/api/messaging.php
- src/Appwrite/Platform/Modules/Compute/Base.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
- src/Appwrite/Platform/Workers/Certificates.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
- src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
- src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
- CLAUDE.md
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
Show resolved
Hide resolved
1fefbaa to
da4d83e
Compare
Optimize updateDocument() calls across the codebase to pass only changed attributes as sparse Document objects rather than full documents. This is more efficient because updateDocument() internally performs array_merge(). Changes: - Updated 58 files to use sparse Document objects - Added Performance Patterns section to AGENTS.md with optimization guidelines - Applied pattern to Workers, Functions, Sites, Teams, VCS modules - Updated app/controllers/api files (account, users, messaging) - Updated app infrastructure files (realtime, general, init/resources, shared/api) Exceptions maintained: - Migration files (need full document updates by design) - Cases with 6+ attributes (marginal benefit) - Complex nested relationship logic
da4d83e to
8b026d3
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (5)
app/controllers/api/users.php (3)
1579-1582:⚠️ Potential issue | 🟠 MajorMissing
searchfield update breaks user discovery.The
phonefield is part of the user'ssearchindex. After this sparse update, users won't be findable by their new phone via/v1/users?search=.Proposed fix
$user ->setAttribute('phone', $number) - ->setAttribute('phoneVerification', false) + ->setAttribute('phoneVerification', false) + ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('email'), $number, $user->getAttribute('name')])) ; // ... later in the try block: $user = $dbForProject->updateDocument('users', $user->getId(), new Document([ 'phone' => $user->getAttribute('phone'), 'phoneVerification' => $user->getAttribute('phoneVerification'), + 'search' => $user->getAttribute('search'), ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1579 - 1582, The update call to $dbForProject->updateDocument('users', $user->getId(), new Document([...])) omits the user's `search` field so the new phone won't be indexed; modify that Document to also set the `search` field (e.g. include the updated phone value from $user->getAttribute('phone') in the `search` payload) when updating the 'users' document so searches by phone continue to work.
1481-1489:⚠️ Potential issue | 🟠 MajorMissing
searchfield update breaks user discovery.The
searchindex. After this sparse update, users won't be findable by their new email via/v1/users?search=.Proposed fix
$user ->setAttribute('email', $email) ->setAttribute('emailVerification', false) ->setAttribute('emailCanonical', $emailCanonical?->getCanonical()) ->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported()) ->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate()) ->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable()) - ->setAttribute('emailIsFree', $emailCanonical?->isFree()) + ->setAttribute('emailIsFree', $emailCanonical?->isFree()) + ->setAttribute('search', implode(' ', [$user->getId(), $email, $user->getAttribute('phone'), $user->getAttribute('name')])) ; try { $user = $dbForProject->updateDocument('users', $user->getId(), new Document([ 'email' => $user->getAttribute('email'), 'emailVerification' => $user->getAttribute('emailVerification'), 'emailCanonical' => $user->getAttribute('emailCanonical'), 'emailIsCanonical' => $user->getAttribute('emailIsCanonical'), 'emailIsCorporate' => $user->getAttribute('emailIsCorporate'), 'emailIsDisposable' => $user->getAttribute('emailIsDisposable'), 'emailIsFree' => $user->getAttribute('emailIsFree'), + 'search' => $user->getAttribute('search'), ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1481 - 1489, The sparse update to the users document omits the user's searchable "search" field so searches by new email will fail; inside the call to $dbForProject->updateDocument('users', $user->getId(), new Document([...])) include an updated 'search' value (e.g. rebuild or merge $user->getAttribute('search') and the new email string) so the user's email is present in the search index after the update; update the Document payload in this block to set 'search' appropriately using the $user variable and existing search attributes.
1290-1292:⚠️ Potential issue | 🟠 MajorMissing
searchfield update breaks user discovery.The
namefield is part of the user'ssearchindex (see line 161:'search' => implode(' ', [$userId, $email, $phone, $name])). After this sparse update, users won't be findable by their new name via/v1/users?search=.Proposed fix
$user->setAttribute('name', $name); +$user->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('email'), $user->getAttribute('phone'), $name])); -$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['name' => $user->getAttribute('name')])); +$user = $dbForProject->updateDocument('users', $user->getId(), new Document([ + 'name' => $user->getAttribute('name'), + 'search' => $user->getAttribute('search'), +]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/users.php` around lines 1290 - 1292, The update currently only writes the 'name' field which leaves the user's 'search' index stale (the code earlier builds 'search' from userId, email, phone, name). When updating the user in updateDocument('users', ...), also set the 'search' field to the concatenation used elsewhere: build the new search string from the user's id and current email, phone and the new name (use $user->getId(), $user->getAttribute('email'), $user->getAttribute('phone'), and the updated name), and pass both 'name' and 'search' in the Document passed to updateDocument so searches reflect the new name.app/controllers/api/account.php (2)
4521-4525:⚠️ Potential issue | 🟠 MajorOnly write
expiredwhen this request resets it.
createPushTarget()does not initializeexpired, so Line 4523 can still write backnullhere. Build the sparse payload incrementally and addexpiredonly in the branch that sets it.💡 Suggested fix
- $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([ - 'identifier' => $target->getAttribute('identifier'), - 'expired' => $target->getAttribute('expired'), - 'name' => $target->getAttribute('name'), - ])); + $updates = [ + 'name' => $target->getAttribute('name'), + ]; + + if ($identifier) { + $updates['identifier'] = $target->getAttribute('identifier'); + $updates['expired'] = false; + } + + $target = $dbForProject->updateDocument('targets', $target->getId(), new Document($updates));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 4521 - 4525, The updateDocument call currently writes 'expired' unconditionally, which may persist null from createPushTarget(); modify the payload construction before calling $dbForProject->updateDocument('targets', $target->getId(), ...) to build a sparse associative array/document incrementally (e.g. $patch = ['identifier'=>..., 'name'=>...]) and only add 'expired' to $patch when the request explicitly resets/sets it (the branch in createPushTarget()/request handling that intends to change expired). Then pass that sparse $patch/document to updateDocument so 'expired' is not written when unset.
291-294:⚠️ Potential issue | 🟠 MajorOnly persist the verification flag that actually changed.
This still writes both flags back. For email and magic-link flows,
phoneVerificationis not guaranteed to exist on the loaded user, so this sparse update can overwrite it withnull.💡 Suggested fix
- $dbForProject->updateDocument('users', $user->getId(), new Document([ - 'emailVerification' => $user->getAttribute('emailVerification'), - 'phoneVerification' => $user->getAttribute('phoneVerification'), - ])); + $updates = []; + + if (\in_array($verifiedToken->getAttribute('type'), [TOKEN_TYPE_MAGIC_URL, TOKEN_TYPE_EMAIL], true)) { + $updates['emailVerification'] = true; + } + + if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) { + $updates['phoneVerification'] = true; + } + + if ($updates !== []) { + $dbForProject->updateDocument('users', $user->getId(), new Document($updates)); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/controllers/api/account.php` around lines 291 - 294, The update currently always writes both 'emailVerification' and 'phoneVerification' which can overwrite missing fields with null; instead build an updates array that only includes keys that actually changed or are present on the loaded user and pass that to $dbForProject->updateDocument('users', $user->getId(), new Document(...)). Use $user->getAttribute('emailVerification') and $user->getAttribute('phoneVerification') (or an existence/changed check) to conditionally add each field so updateDocument and Document are only passed the verification flag(s) that should be persisted.
🧹 Nitpick comments (6)
src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php (1)
67-71: Dead code:setAttributecalls on lines 67-69 are now unused.These lines modify
$team, but that object is never used afterward—line 71 creates a new sparseDocumentwith the same values and reassigns$teamto the result ofupdateDocument(). The original$teammodifications are discarded.Remove the redundant
setAttributecalls to avoid confusion:♻️ Proposed fix
if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); } - $team - ->setAttribute('name', $name) - ->setAttribute('search', implode(' ', [$teamId, $name])); - - $team = $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])])); + $team = $dbForProject->updateDocument('teams', $team->getId(), new Document([ + 'name' => $name, + 'search' => implode(' ', [$teamId, $name]) + ])); $queueForEvents->setParam('teamId', $team->getId());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php` around lines 67 - 71, The three redundant mutating calls to $team->setAttribute('name', $name) and ->setAttribute('search', ...) are dead code because $team is immediately overwritten by the result of $dbForProject->updateDocument(...); remove those setAttribute lines and keep the existing call to $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])])) so the update is performed only once; if the original intent was to update the in-memory $team before persisting, instead pass the modified $team to updateDocument or merge its attributes into the Document, otherwise simply delete the setAttribute calls.src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php (1)
112-121: Same redundant attribute-setting pattern.Similar to the callback handler, you can pass values directly to the sparse Document instead of setting them on
$installationfirst:♻️ Suggested simplification
- $installation = $installation - ->setAttribute('personalAccessToken', $accessToken) - ->setAttribute('personalRefreshToken', $refreshToken) - ->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); - - $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([ - 'personalAccessToken' => $installation->getAttribute('personalAccessToken'), - 'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'), - 'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'), - ])); + $accessTokenExpiry = DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')); + + $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([ + 'personalAccessToken' => $accessToken, + 'personalRefreshToken' => $refreshToken, + 'personalAccessTokenExpiry' => $accessTokenExpiry, + ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php` around lines 112 - 121, The code redundantly calls setAttribute on $installation before constructing the sparse Document; instead, remove the intermediate setAttribute calls and pass the values directly into the Document passed to dbForPlatform->updateDocument. Locate the block using $installation->setAttribute('personalAccessToken', ...), setAttribute('personalRefreshToken', ...), setAttribute('personalAccessTokenExpiry', ...) and replace it by building the new Document with 'personalAccessToken' => $accessToken, 'personalRefreshToken' => $refreshToken, and 'personalAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')), then call $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([...])) without mutating $installation via setAttribute.src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php (1)
140-150: Redundant attribute setting before sparse update.The values are available directly from
$deployment:♻️ Suggested simplification
- $site = $site - ->setAttribute('latestDeploymentId', $deployment->getId()) - ->setAttribute('latestDeploymentInternalId', $deployment->getSequence()) - ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt()) - ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); - $dbForProject->updateDocument('sites', $site->getId(), new Document([ - 'latestDeploymentId' => $site->getAttribute('latestDeploymentId'), - 'latestDeploymentInternalId' => $site->getAttribute('latestDeploymentInternalId'), - 'latestDeploymentCreatedAt' => $site->getAttribute('latestDeploymentCreatedAt'), - 'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'), - ])); + $dbForProject->updateDocument('sites', $site->getId(), new Document([ + 'latestDeploymentId' => $deployment->getId(), + 'latestDeploymentInternalId' => $deployment->getSequence(), + 'latestDeploymentCreatedAt' => $deployment->getCreatedAt(), + 'latestDeploymentStatus' => $deployment->getAttribute('status', ''), + ]));Note: If
$siteis used later with the updated attributes, keep thesetAttributecalls but consider removing the redundantgetAttributereads in the Document constructor.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php` around lines 140 - 150, The code redundantly sets attributes on $site and then reads them back via $site->getAttribute when constructing the Document for dbForProject->updateDocument; instead either remove the intermediate setAttribute calls and pass values directly from $deployment (use $deployment->getId(), ->getSequence(), ->getCreatedAt(), ->getAttribute('status')) into the Document constructor, or if $site is needed later keep the setAttribute calls but change the Document constructor to use $site->getAttribute only once (or better yet use the $deployment values directly) to avoid redundant reads; update the block around setAttribute, updateDocument, and Document to reflect this choice.src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php (1)
324-334: Redundant attribute setting before sparse update.Values are available directly from
$deployment:♻️ Suggested simplification
- $resource = $resource - ->setAttribute('latestDeploymentId', $deployment->getId()) - ->setAttribute('latestDeploymentInternalId', $deployment->getSequence()) - ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt()) - ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); - $authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document([ - 'latestDeploymentId' => $resource->getAttribute('latestDeploymentId'), - 'latestDeploymentInternalId' => $resource->getAttribute('latestDeploymentInternalId'), - 'latestDeploymentCreatedAt' => $resource->getAttribute('latestDeploymentCreatedAt'), - 'latestDeploymentStatus' => $resource->getAttribute('latestDeploymentStatus'), - ]))); + $authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document([ + 'latestDeploymentId' => $deployment->getId(), + 'latestDeploymentInternalId' => $deployment->getSequence(), + 'latestDeploymentCreatedAt' => $deployment->getCreatedAt(), + 'latestDeploymentStatus' => $deployment->getAttribute('status', ''), + ])));If
$resourceattributes are needed later in the method (e.g., for the VCS comment/preview logic), retain thesetAttributecalls.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php` around lines 324 - 334, The block redundantly writes deployment values into $resource before doing a sparse update; either remove the setAttribute(...) chain and pass values directly from $deployment to the authorization->skip(... $dbForProject->updateDocument(...)) call, or if later code relies on $resource having those attributes, keep the setAttribute calls—so update the sparse update to use $deployment->getId(), $deployment->getSequence(), $deployment->getCreatedAt(), and $deployment->getAttribute('status','') directly (in the call inside authorization->skip) or leave the setAttribute chain intact if $resource attributes are used later in this method (e.g., for VCS comment/preview logic).src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php (1)
149-161: Redundant attribute setting before sparse update.Lines 149-154 set attributes on
$installation, then lines 156-160 read them back viagetAttribute(). Since you're using a sparse update anyway, pass the values directly to avoid the intermediate mutation:♻️ Suggested simplification
- $installation = $installation - ->setAttribute('organization', $owner) - ->setAttribute('personal', $personal) - ->setAttribute('personalRefreshToken', $refreshToken) - ->setAttribute('personalAccessToken', $accessToken) - ->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry); - $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([ - 'organization' => $installation->getAttribute('organization'), - 'personal' => $installation->getAttribute('personal'), - 'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'), - 'personalAccessToken' => $installation->getAttribute('personalAccessToken'), - 'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'), - ])); + $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([ + 'organization' => $owner, + 'personal' => $personal, + 'personalRefreshToken' => $refreshToken, + 'personalAccessToken' => $accessToken, + 'personalAccessTokenExpiry' => $accessTokenExpiry, + ]));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php` around lines 149 - 161, The code redundantly calls setAttribute on $installation then immediately reads them back for a sparse update; remove the intermediate mutations and directly pass the intended values ($owner, $personal, $refreshToken, $accessToken, $accessTokenExpiry) into the dbForPlatform->updateDocument call (using $installation->getId() to identify the record), eliminating the setAttribute(...) lines and replacing the new Document([...]) payload to use those variables directly.src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)
282-285: Consider extracting a helper for deployment patch + realtime emit.This worker now repeats the same
updateDocument(... new Document([...]))andqueueForRealtime->setPayload(...)->trigger()sequence many times. A small helper would reduce noise and make future status/log changes easier to keep consistent.Also applies to: 372-376, 490-496, 544-548, 563-565, 841-843, 929-936, 952-955, 1198-1203, 1510-1512, 1531-1533
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php` around lines 282 - 285, Extract a small helper (e.g., patchDeploymentAndEmit or updateDeploymentWithRealtime) that accepts the db client ($dbForProject), the Deployment Document ($deployment or $deployment->getId()), an associative array of fields to set (or specific params like status, timestamps, logs), and the queueForRealtime instance; inside the helper call $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([...])) and immediately call $queueForRealtime->setPayload(...)->trigger() with the same payload, then replace all repeated sequences (the calls around updateDocument and queueForRealtime->setPayload->trigger at the locations referencing updateDocument, $deployment, and queueForRealtime) with calls to this new helper to keep behavior identical while centralizing payload construction and realtime emission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The updateDocument call currently writes 'expired'
unconditionally, which may persist null from createPushTarget(); modify the
payload construction before calling $dbForProject->updateDocument('targets',
$target->getId(), ...) to build a sparse associative array/document
incrementally (e.g. $patch = ['identifier'=>..., 'name'=>...]) and only add
'expired' to $patch when the request explicitly resets/sets it (the branch in
createPushTarget()/request handling that intends to change expired). Then pass
that sparse $patch/document to updateDocument so 'expired' is not written when
unset.
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' which can overwrite missing fields
with null; instead build an updates array that only includes keys that actually
changed or are present on the loaded user and pass that to
$dbForProject->updateDocument('users', $user->getId(), new Document(...)). Use
$user->getAttribute('emailVerification') and
$user->getAttribute('phoneVerification') (or an existence/changed check) to
conditionally add each field so updateDocument and Document are only passed the
verification flag(s) that should be persisted.
In `@app/controllers/api/users.php`:
- Around line 1579-1582: The update call to
$dbForProject->updateDocument('users', $user->getId(), new Document([...]))
omits the user's `search` field so the new phone won't be indexed; modify that
Document to also set the `search` field (e.g. include the updated phone value
from $user->getAttribute('phone') in the `search` payload) when updating the
'users' document so searches by phone continue to work.
- Around line 1481-1489: The sparse update to the users document omits the
user's searchable "search" field so searches by new email will fail; inside the
call to $dbForProject->updateDocument('users', $user->getId(), new
Document([...])) include an updated 'search' value (e.g. rebuild or merge
$user->getAttribute('search') and the new email string) so the user's email is
present in the search index after the update; update the Document payload in
this block to set 'search' appropriately using the $user variable and existing
search attributes.
- Around line 1290-1292: The update currently only writes the 'name' field which
leaves the user's 'search' index stale (the code earlier builds 'search' from
userId, email, phone, name). When updating the user in updateDocument('users',
...), also set the 'search' field to the concatenation used elsewhere: build the
new search string from the user's id and current email, phone and the new name
(use $user->getId(), $user->getAttribute('email'), $user->getAttribute('phone'),
and the updated name), and pass both 'name' and 'search' in the Document passed
to updateDocument so searches reflect the new name.
---
Nitpick comments:
In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 282-285: Extract a small helper (e.g., patchDeploymentAndEmit or
updateDeploymentWithRealtime) that accepts the db client ($dbForProject), the
Deployment Document ($deployment or $deployment->getId()), an associative array
of fields to set (or specific params like status, timestamps, logs), and the
queueForRealtime instance; inside the helper call
$dbForProject->updateDocument('deployments', $deployment->getId(), new
Document([...])) and immediately call
$queueForRealtime->setPayload(...)->trigger() with the same payload, then
replace all repeated sequences (the calls around updateDocument and
queueForRealtime->setPayload->trigger at the locations referencing
updateDocument, $deployment, and queueForRealtime) with calls to this new helper
to keep behavior identical while centralizing payload construction and realtime
emission.
In `@src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php`:
- Around line 140-150: The code redundantly sets attributes on $site and then
reads them back via $site->getAttribute when constructing the Document for
dbForProject->updateDocument; instead either remove the intermediate
setAttribute calls and pass values directly from $deployment (use
$deployment->getId(), ->getSequence(), ->getCreatedAt(),
->getAttribute('status')) into the Document constructor, or if $site is needed
later keep the setAttribute calls but change the Document constructor to use
$site->getAttribute only once (or better yet use the $deployment values
directly) to avoid redundant reads; update the block around setAttribute,
updateDocument, and Document to reflect this choice.
In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php`:
- Around line 67-71: The three redundant mutating calls to
$team->setAttribute('name', $name) and ->setAttribute('search', ...) are dead
code because $team is immediately overwritten by the result of
$dbForProject->updateDocument(...); remove those setAttribute lines and keep the
existing call to $dbForProject->updateDocument('teams', $team->getId(), new
Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])])) so the
update is performed only once; if the original intent was to update the
in-memory $team before persisting, instead pass the modified $team to
updateDocument or merge its attributes into the Document, otherwise simply
delete the setAttribute calls.
In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php`:
- Around line 149-161: The code redundantly calls setAttribute on $installation
then immediately reads them back for a sparse update; remove the intermediate
mutations and directly pass the intended values ($owner, $personal,
$refreshToken, $accessToken, $accessTokenExpiry) into the
dbForPlatform->updateDocument call (using $installation->getId() to identify the
record), eliminating the setAttribute(...) lines and replacing the new
Document([...]) payload to use those variables directly.
In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php`:
- Around line 324-334: The block redundantly writes deployment values into
$resource before doing a sparse update; either remove the setAttribute(...)
chain and pass values directly from $deployment to the authorization->skip(...
$dbForProject->updateDocument(...)) call, or if later code relies on $resource
having those attributes, keep the setAttribute calls—so update the sparse update
to use $deployment->getId(), $deployment->getSequence(),
$deployment->getCreatedAt(), and $deployment->getAttribute('status','') directly
(in the call inside authorization->skip) or leave the setAttribute chain intact
if $resource attributes are used later in this method (e.g., for VCS
comment/preview logic).
In
`@src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php`:
- Around line 112-121: The code redundantly calls setAttribute on $installation
before constructing the sparse Document; instead, remove the intermediate
setAttribute calls and pass the values directly into the Document passed to
dbForPlatform->updateDocument. Locate the block using
$installation->setAttribute('personalAccessToken', ...),
setAttribute('personalRefreshToken', ...),
setAttribute('personalAccessTokenExpiry', ...) and replace it by building the
new Document with 'personalAccessToken' => $accessToken, 'personalRefreshToken'
=> $refreshToken, and 'personalAccessTokenExpiry' => DateTime::addSeconds(new
\DateTime(), (int)$oauth2->getAccessTokenExpiry('')), then call
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without mutating $installation via setAttribute.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 67c99bb8-307d-424b-a537-43657f1a2e72
📒 Files selected for processing (59)
AGENTS.mdCLAUDE.mdapp/controllers/api/account.phpapp/controllers/api/messaging.phpapp/controllers/api/users.phpapp/controllers/general.phpapp/controllers/shared/api.phpapp/init/resources.phpapp/realtime.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.phpsrc/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.phpsrc/Appwrite/Platform/Modules/Compute/Base.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.phpsrc/Appwrite/Platform/Modules/Databases/Workers/Databases.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Functions/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Functions/Workers/Builds.phpsrc/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.phpsrc/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Create.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.phpsrc/Appwrite/Platform/Modules/Sites/Http/Variables/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.phpsrc/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.phpsrc/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.phpsrc/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.phpsrc/Appwrite/Platform/Tasks/Interval.phpsrc/Appwrite/Platform/Workers/Certificates.phpsrc/Appwrite/Platform/Workers/Messaging.phpsrc/Appwrite/Platform/Workers/Migrations.phpsrc/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (25)
- CLAUDE.md
- app/controllers/api/messaging.php
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
- src/Appwrite/Platform/Workers/Certificates.php
- src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
- src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
- src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
- src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
- AGENTS.md
- src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
- src/Appwrite/Platform/Workers/Messaging.php
- src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
- src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
- src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
- src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
- src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
- app/realtime.php
- src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
- src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
Summary
This PR optimizes
updateDocument()calls across the codebase to pass only changed attributes as sparseDocumentobjects rather than full documents. This is more efficient becauseupdateDocument()internally performsarray_merge($old, $new), so passing only changed fields reduces overhead.Changes
Pattern Applied
Before (inefficient):
After (optimized):
Exceptions Maintained
Per the optimization guidelines, the following were intentionally excluded:
array_merge()withgetArrayCopy()Testing
docker compose exec appwrite php vendor/bin/phpunitcomposer formatto ensure code style complianceRelated