Skip to content

Add platform migration support#154

Open
premtsd-code wants to merge 5 commits intomainfrom
feat-platform-db-access
Open

Add platform migration support#154
premtsd-code wants to merge 5 commits intomainfrom
feat-platform-db-access

Conversation

@premtsd-code
Copy link
Contributor

@premtsd-code premtsd-code commented Mar 3, 2026

Summary

  • Add Platform resource class under Resources/Integrations with support for all platform types
  • Add GROUP_INTEGRATIONS resource group and TYPE_PLATFORM constant to Resource
  • Implement exportPlatforms() in Sources/Appwrite using console key authentication with caching
  • Implement importPlatformResource() in Destinations/Appwrite

Summary by CodeRabbit

  • New Features

    • Added platform integration as a first-class migratable resource: export, report, and import flows now handle platform integrations with console-authenticated export and duplicate-aware import.
  • Tests

    • Updated mocks and unit adapters to include platform integration resources and export coverage.
  • Chores

    • Added integration export handlers/placeholders across source adapters and wired integration import/reporting flow in the destination.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds platform/integration support across the migration system. Introduces Resource::TYPE_PLATFORM and a new Platform resource class. Renames Transfer group GROUP_SETTINGS → GROUP_INTEGRATIONS and registers platform resources in public/root lists. Extends Source with getIntegrationsBatchSize and abstract exportGroupIntegrations; Appwrite Source adds console-key fetching/caching (setConsoleKey, getConsoleHeaders), reporting and export flows for platforms. Appwrite Destination accepts a dbForPlatform/projectInternalId, adds importIntegrationsResource and createPlatform (duplicate checks, timestamp preservation, cache purge). CSV, Firebase, JSON, NHost add stub exportGroupIntegrations methods. Tests/mocks updated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add platform migration support' directly and clearly describes the main change: adding support for platform migrations across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-platform-db-access
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/Migration/Sources/Firebase.php (1)

816-819: Minor: Inconsistent exception message casing.

The exception message uses 'Not implemented' (lowercase 'i'), while other sources (CSV, JSON, NHost) use 'Not Implemented' (uppercase 'I'). Consider standardizing for consistency.

Proposed fix
 protected function exportGroupIntegrations(int $batchSize, array $resources): void
 {
-    throw new \Exception('Not implemented');
+    throw new \Exception('Not Implemented');
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Firebase.php` around lines 816 - 819, The exception
message in Firebase::exportGroupIntegrations currently uses 'Not implemented';
update the throw in the protected method exportGroupIntegrations(int $batchSize,
array $resources) to throw the same canonical message 'Not Implemented' used by
other sources so the exception casing is consistent across implementations
(locate the throw new \Exception(...) inside exportGroupIntegrations and change
the string).
src/Migration/Sources/Appwrite.php (2)

2065-2073: Consider implementing pagination using $batchSize parameter.

The $batchSize parameter is unused and exportPlatforms fetches all platforms in a single API call. Other export methods implement pagination for scalability. If the platforms API supports pagination, consider adding it:

-private function exportPlatforms(array $consoleHeaders): void
+private function exportPlatforms(array $consoleHeaders, int $batchSize = 25): void
 {
-    $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
+    $lastPlatform = null;
+
+    while (true) {
+        $queries = [Query::limit($batchSize)];
+        if ($lastPlatform) {
+            $queries[] = Query::cursorAfter($lastPlatform);
+        }
+        
+        $response = $this->call('GET', '/projects/' . $this->project . '/platforms?' . http_build_query(['queries' => $queries]), $consoleHeaders);
+        // ... process platforms ...
+        
+        if (count($response['platforms']) < $batchSize) {
+            break;
+        }
+    }

If the API doesn't support pagination or platform counts are expected to be small, consider documenting this or removing $batchSize from the method signature for clarity.

Also applies to: 2105-2129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2065 - 2073,
exportGroupIntegrations currently ignores the $batchSize parameter and calls
exportPlatforms which fetches all platforms at once; update exportPlatforms
(and/or the call site in exportGroupIntegrations) to implement pageable fetching
using $batchSize (e.g., loop with offset/page tokens until no more results) and
yield/return batches compatible with exportWithConsoleHeaders, or if the
Appwrite platforms API has no pagination, remove $batchSize from
exportGroupIntegrations’ signature or add a code comment documenting the lack of
pagination; reference the exportGroupIntegrations method, exportPlatforms
function, and exportWithConsoleHeaders helper when making the change.

2043-2058: Consider using $resourceIds parameter for consistency.

The $resourceIds parameter is unused (as flagged by static analysis). Other report* methods like reportAuth, reportStorage, etc. use this parameter to filter results. For consistency, consider adding filtering support:

 private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void
 {
     if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
         $consoleHeaders = $this->getConsoleHeaders();

         if ($consoleHeaders === null) {
             return;
         }

         try {
-            $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
+            $queries = [];
+            if (!empty($resourceIds[Resource::TYPE_PLATFORM])) {
+                // Note: Add filtering if the API supports it
+            }
+            $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
             $report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0;

Alternatively, remove the unused parameter if filtering isn't applicable for platforms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2043 - 2058, The
reportIntegrations method currently ignores the $resourceIds parameter; either
use it to filter the platforms count or remove the unused parameter. To fix,
update reportIntegrations (method name: reportIntegrations) to apply
$resourceIds when calling the platforms endpoint (e.g. include IDs or a filter
in the GET to '/projects/{project}/platforms' and compute
$report[Resource::TYPE_PLATFORM] from the filtered response) or delete the
$resourceIds parameter from the method signature and all callers if platform
filtering is not supported; ensure the try/catch and assignment to
$report[Resource::TYPE_PLATFORM] remain consistent with other report* methods
(e.g., reportAuth/reportStorage) when implementing the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Migration/Sources/Appwrite.php`:
- Around line 2065-2073: exportGroupIntegrations currently ignores the
$batchSize parameter and calls exportPlatforms which fetches all platforms at
once; update exportPlatforms (and/or the call site in exportGroupIntegrations)
to implement pageable fetching using $batchSize (e.g., loop with offset/page
tokens until no more results) and yield/return batches compatible with
exportWithConsoleHeaders, or if the Appwrite platforms API has no pagination,
remove $batchSize from exportGroupIntegrations’ signature or add a code comment
documenting the lack of pagination; reference the exportGroupIntegrations
method, exportPlatforms function, and exportWithConsoleHeaders helper when
making the change.
- Around line 2043-2058: The reportIntegrations method currently ignores the
$resourceIds parameter; either use it to filter the platforms count or remove
the unused parameter. To fix, update reportIntegrations (method name:
reportIntegrations) to apply $resourceIds when calling the platforms endpoint
(e.g. include IDs or a filter in the GET to '/projects/{project}/platforms' and
compute $report[Resource::TYPE_PLATFORM] from the filtered response) or delete
the $resourceIds parameter from the method signature and all callers if platform
filtering is not supported; ensure the try/catch and assignment to
$report[Resource::TYPE_PLATFORM] remain consistent with other report* methods
(e.g., reportAuth/reportStorage) when implementing the change.

In `@src/Migration/Sources/Firebase.php`:
- Around line 816-819: The exception message in
Firebase::exportGroupIntegrations currently uses 'Not implemented'; update the
throw in the protected method exportGroupIntegrations(int $batchSize, array
$resources) to throw the same canonical message 'Not Implemented' used by other
sources so the exception casing is consistent across implementations (locate the
throw new \Exception(...) inside exportGroupIntegrations and change the string).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 037bf4b and 06ea808.

📒 Files selected for processing (12)
  • src/Migration/Destinations/Appwrite.php
  • src/Migration/Resource.php
  • src/Migration/Resources/Integrations/Platform.php
  • src/Migration/Source.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Firebase.php
  • src/Migration/Sources/JSON.php
  • src/Migration/Sources/NHost.php
  • src/Migration/Transfer.php
  • tests/Migration/Unit/Adapters/MockDestination.php
  • tests/Migration/Unit/Adapters/MockSource.php

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/Migration/Sources/Appwrite.php`:
- Around line 2043-2059: Ensure reportIntegrations always initializes
$report[Resource::TYPE_PLATFORM] to 0 before any early returns (e.g., when
getConsoleHeaders() returns null) and honor the optional $resourceIds filter:
after successfully calling $this->call('GET', '/projects/.../platforms',
$consoleHeaders) in reportIntegrations, if $resourceIds is non-empty compute the
count as the number of returned platform IDs that intersect $resourceIds
(otherwise use $response['total'] ?? 0), then assign that value to
$report[Resource::TYPE_PLATFORM]; keep the catch block to set it to 0 on
failure.
- Around line 2065-2074: The exportPlatforms call currently ignores pagination
and batchSize; change exportPlatforms to accept an int $batchSize parameter and
implement the same paginated loop pattern used by other exporters (e.g.,
exportSites): build a Query::limit($batchSize) and use
Query::cursorAfter($cursor) to iterate until no more results, accumulate/emit
each page via exportWithConsoleHeaders in exportGroupIntegrations, and honor
root filtering by checking $this->rootResourceId and $this->rootResourceType
(apply a query filter or skip non-matching platform records) so platforms are
fetched in pages and scoped to the requested root resource.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5286cc3-043e-4038-981d-5bf3f1e49d69

📥 Commits

Reviewing files that changed from the base of the PR and between 06ea808 and 666f015.

📒 Files selected for processing (1)
  • src/Migration/Sources/Appwrite.php

Comment on lines +2043 to +2059
private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void
{
if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
$consoleHeaders = $this->getConsoleHeaders();

if ($consoleHeaders === null) {
return;
}

try {
$response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
$report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0;
} catch (\Throwable) {
$report[Resource::TYPE_PLATFORM] = 0;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Always initialize platform report count, and honor requested IDs.

When Resource::TYPE_PLATFORM is requested, reportIntegrations() can return without setting $report[Resource::TYPE_PLATFORM] (Line 2048-2050). Also, $resourceIds is currently unused, so filtered runs can report incorrect totals.

💡 Suggested fix
 private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void
 {
-    if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
-        $consoleHeaders = $this->getConsoleHeaders();
-
-        if ($consoleHeaders === null) {
-            return;
-        }
-
-        try {
-            $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
-            $report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0;
-        } catch (\Throwable) {
-            $report[Resource::TYPE_PLATFORM] = 0;
-        }
-    }
+    if (!\in_array(Resource::TYPE_PLATFORM, $resources, true)) {
+        return;
+    }
+
+    $report[Resource::TYPE_PLATFORM] = 0;
+    $consoleHeaders = $this->getConsoleHeaders();
+    if ($consoleHeaders === null) {
+        return;
+    }
+
+    try {
+        $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
+        $platforms = $response['platforms'] ?? [];
+
+        if (!empty($resourceIds[Resource::TYPE_PLATFORM])) {
+            $allowed = \array_flip((array) $resourceIds[Resource::TYPE_PLATFORM]);
+            $platforms = \array_filter($platforms, fn(array $p): bool => isset($allowed[$p['$id'] ?? '']));
+            $report[Resource::TYPE_PLATFORM] = \count($platforms);
+            return;
+        }
+
+        $report[Resource::TYPE_PLATFORM] = $response['total'] ?? \count($platforms);
+    } catch (\Throwable) {
+        // keep fallback 0
+    }
 }
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 2043-2043: Avoid unused parameters such as '$resourceIds'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2043 - 2059, Ensure
reportIntegrations always initializes $report[Resource::TYPE_PLATFORM] to 0
before any early returns (e.g., when getConsoleHeaders() returns null) and honor
the optional $resourceIds filter: after successfully calling $this->call('GET',
'/projects/.../platforms', $consoleHeaders) in reportIntegrations, if
$resourceIds is non-empty compute the count as the number of returned platform
IDs that intersect $resourceIds (otherwise use $response['total'] ?? 0), then
assign that value to $report[Resource::TYPE_PLATFORM]; keep the catch block to
set it to 0 on failure.

Comment on lines +2065 to +2074
protected function exportGroupIntegrations(int $batchSize, array $resources): void
{
if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
$this->exportWithConsoleHeaders(
Resource::TYPE_PLATFORM,
Transfer::GROUP_INTEGRATIONS,
$this->exportPlatforms(...)
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For Appwrite API/SDK v19, does GET /projects/{projectId}/platforms support pagination via queries[]=limit(...) and queries[]=cursorAfter(...)? If yes, what is the exact query format?

💡 Result:

Yes — if you call GET /v1/projects/{projectId}/platforms with the queries array, you can paginate the same way as other list endpoints: limit + cursorAfter. The REST API expects each query as an escaped JSON query string passed via repeated queries[] parameters. (appwrite.io)

REST query format (exact)

GET /v1/projects/{projectId}/platforms?queries[]={"method":"limit","values":[25]}&queries[]={"method":"cursorAfter","values":["<LAST_PLATFORM_ID>"]}

URL-encode the JSON when sending it over HTTP (spaces/quotes/braces). Appwrite documents this as “escaped JSON strings” passed as queries[] and shows cursorAfter using {"method":"cursorAfter","values":["rowId"]}. (appwrite.io)

SDK format (v19-style)

Pass an array of query strings generated by the SDK Query helper:

queries: [
  Query.limit(25),
  Query.cursorAfter('<LAST_PLATFORM_ID>')
]

(These helpers generate the same JSON query strings shown above.) (appwrite.io)

Citations:


🏁 Script executed:

# First, let's check the file structure and read the relevant lines
wc -l src/Migration/Sources/Appwrite.php

Repository: utopia-php/migration

Length of output: 103


🏁 Script executed:

# Read the code around lines 2065-2074 and 2105-2129
sed -n '2065,2074p' src/Migration/Sources/Appwrite.php
echo "---"
sed -n '2105,2129p' src/Migration/Sources/Appwrite.php

Repository: utopia-php/migration

Length of output: 1238


🏁 Script executed:

# Find the exportPlatforms method to see the actual implementation
grep -n "protected function exportPlatforms\|private function exportPlatforms" src/Migration/Sources/Appwrite.php

Repository: utopia-php/migration

Length of output: 134


🏁 Script executed:

# Search for other export methods to see pagination patterns
grep -n "private function export" src/Migration/Sources/Appwrite.php | head -20

Repository: utopia-php/migration

Length of output: 1299


🏁 Script executed:

# Look at one example exporter to see if it uses pagination
sed -n '1800,1900p' src/Migration/Sources/Appwrite.php | head -50

Repository: utopia-php/migration

Length of output: 1549


🏁 Script executed:

# Search for a specific exporter method like exportServers or exportKeys
grep -A 20 "private function exportServers" src/Migration/Sources/Appwrite.php | head -30

Repository: utopia-php/migration

Length of output: 46


🏁 Script executed:

# Search for rootResourceId usage to verify root resource handling claim
grep -n "rootResourceId\|rootResourceType" src/Migration/Sources/Appwrite.php | head -20

Repository: utopia-php/migration

Length of output: 1678


exportPlatforms() does not paginate results and ignores the batch size parameter.

Unlike other resource exporters in this class, this method makes a single API call without pagination support. If the Appwrite API returns paginated results, platform data will be truncated. Additionally, it does not support filtering by root resource ID as other exporters do.

The method signature should accept int $batchSize, and the implementation should:

  • Use Query::limit($batchSize) and Query::cursorAfter() pagination queries
  • Loop until all results are fetched
  • Support root resource filtering via $this->rootResourceId and $this->rootResourceType checks

See other exporters in this file (e.g., exportSites at line 1827) for the established pagination pattern to follow.

🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 2065-2065: Avoid unused parameters such as '$batchSize'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2065 - 2074, The
exportPlatforms call currently ignores pagination and batchSize; change
exportPlatforms to accept an int $batchSize parameter and implement the same
paginated loop pattern used by other exporters (e.g., exportSites): build a
Query::limit($batchSize) and use Query::cursorAfter($cursor) to iterate until no
more results, accumulate/emit each page via exportWithConsoleHeaders in
exportGroupIntegrations, and honor root filtering by checking
$this->rootResourceId and $this->rootResourceType (apply a query filter or skip
non-matching platform records) so platforms are fetched in pages and scoped to
the requested root resource.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Migration/Transfer.php (1)

61-95: ⚠️ Potential issue | 🔴 Critical

Resolve unresolved merge-conflict blocks before merge.

Lines 61, 95, 144, 150, and 410–418 still include git conflict markers, which makes this file unparsable and risks dropping either database/messaging groups or the integrations group depending on which side is kept.

🔧 Suggested merge direction
-<<<<<<< HEAD
+    public const GROUP_INTEGRATIONS_RESOURCES = [
+        Resource::TYPE_PLATFORM,
+    ];
+
     public const GROUP_DOCUMENTSDB_RESOURCES = [
         Resource::TYPE_DATABASE_DOCUMENTSDB,
         Resource::TYPE_COLLECTION,
         Resource::TYPE_INDEX,
         Resource::TYPE_DOCUMENT
     ];

     public const GROUP_VECTORSDB_RESOURCES = [
         Resource::TYPE_DATABASE_VECTORSDB,
         Resource::TYPE_COLLECTION,
         Resource::TYPE_ATTRIBUTE,
         Resource::TYPE_INDEX,
         Resource::TYPE_DOCUMENT
     ];

     public const GROUP_DATABASES_RESOURCES = [
         Resource::TYPE_DATABASE,
         Resource::TYPE_DATABASE_DOCUMENTSDB,
         Resource::TYPE_DATABASE_VECTORSDB,
         Resource::TYPE_TABLE,
         Resource::TYPE_INDEX,
         Resource::TYPE_COLUMN,
         Resource::TYPE_ROW,
         Resource::TYPE_DOCUMENT,
         Resource::TYPE_COLLECTION,
         Resource::TYPE_ATTRIBUTE
     ];
-
-    public const GROUP_SETTINGS_RESOURCES = [];
->>>>>>> origin/main
     public const ROOT_RESOURCES = [
         Resource::TYPE_BUCKET,
         Resource::TYPE_DATABASE,
         Resource::TYPE_DATABASE_DOCUMENTSDB,
         Resource::TYPE_DATABASE_VECTORSDB,
         Resource::TYPE_FUNCTION,
         Resource::TYPE_SITE,
         Resource::TYPE_USER,
         Resource::TYPE_TEAM,
+        Resource::TYPE_PLATFORM,
+        Resource::TYPE_PROVIDER,
+        Resource::TYPE_TOPIC,
+        Resource::TYPE_MESSAGE,
     ];
             $resources = match ($service) {
                 self::GROUP_FUNCTIONS => array_merge($resources, self::GROUP_FUNCTIONS_RESOURCES),
                 self::GROUP_SITES => array_merge($resources, self::GROUP_SITES_RESOURCES),
                 self::GROUP_STORAGE => array_merge($resources, self::GROUP_STORAGE_RESOURCES),
                 self::GROUP_GENERAL => array_merge($resources, []),
                 self::GROUP_AUTH => array_merge($resources, self::GROUP_AUTH_RESOURCES),
                 self::GROUP_DATABASES => array_merge($resources, self::GROUP_DATABASES_RESOURCES),
+                self::GROUP_DATABASES_TABLES_DB => array_merge($resources, self::GROUP_TABLESDB_RESOURCES),
+                self::GROUP_DATABASES_DOCUMENTS_DB => array_merge($resources, self::GROUP_DOCUMENTSDB_RESOURCES),
+                self::GROUP_DATABASES_VECTOR_DB => array_merge($resources, self::GROUP_VECTORSDB_RESOURCES),
+                self::GROUP_MESSAGING => array_merge($resources, self::GROUP_MESSAGING_RESOURCES),
                 self::GROUP_INTEGRATIONS => array_merge($resources, self::GROUP_INTEGRATIONS_RESOURCES),
                 default => throw new \Exception('No service group found'),
             };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Transfer.php` around lines 61 - 95, This file still contains
git conflict markers; remove the markers (<<<<<<<, =======, >>>>>>>) and
properly merge the two sides so all intended resource group constants exist:
keep GROUP_INTEGRATIONS_RESOURCES (Resource::TYPE_PLATFORM) and also retain the
new GROUP_DOCUMENTSDB_RESOURCES, GROUP_VECTORSDB_RESOURCES,
GROUP_DATABASES_RESOURCES and GROUP_SETTINGS_RESOURCES definitions, ensuring
there are no duplicate constant names or syntax errors; also search for and
remove conflict markers at the other reported locations (around the blocks that
include lines ~144, ~150 and ~410–418) so the PHP parses cleanly and all groups
are preserved.
🧹 Nitpick comments (1)
src/Migration/Destinations/Appwrite.php (1)

2222-2231: Duplicate detection query may have a race condition.

The findOne check followed by createDocument is not atomic. In high-concurrency scenarios, two imports could pass the existence check simultaneously and both attempt to create the platform. The DuplicateException catch on line 2250 handles this correctly as a fallback, so the logic is safe but could log misleading "already exists" messages for legitimate race conditions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Destinations/Appwrite.php` around lines 2222 - 2231, The
duplicate-detection using $this->dbForPlatform->findOne(...) followed by an
early return causes misleading "Platform already exists" status under race
conditions; remove the early return and allow the subsequent createDocument call
(and its DuplicateException handler) to determine the final outcome, or change
the early-path to only short-circuit when you can guarantee exclusivity;
specifically update the block around $this->dbForPlatform->findOne,
Query::equal(...) and $resource->setStatus(Resource::STATUS_SKIPPED, ...) so it
does not preemptively set SKIPPED on a non-atomic check but instead relies on
the existing DuplicateException handling (the catch near the createDocument
call) to set the SKIPPED status for real duplicates caused by race conditions.
🤖 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/Migration/Destinations/Appwrite.php`:
- Around line 2220-2258: The createPlatform method is missing its closing brace,
causing validateFieldsForIndexes to be parsed inside it; add a closing "}"
immediately after the final "return true;" in the createPlatform method (the
method that begins with "protected function createPlatform(Platform $resource):
bool") so that the subsequent "private function validateFieldsForIndexes(Index
$resource, UtopiaDocument $table, array &$lengths)" is defined as a separate
method; ensure braces/indentation for createPlatform are balanced.
- Line 2240: The project document access uses a non-existent method
getInternalId(); replace that call with getSequence() on the Document returned
by $this->dbForPlatform->getDocument('projects', $this->project) so the
assignment for 'projectInternalId' uses ->getSequence() instead of
->getInternalId(); update the expression where 'projectInternalId' is set (in
the Destinations\Appwrite migration code) to call getSequence().

In `@src/Migration/Resource.php`:
- Around line 114-121: Remove the unresolved merge conflict markers in the
ALL_RESOURCES array inside the Resource class and ensure the array contains all
resource type constants: retain self::TYPE_PLATFORM and add self::TYPE_PROVIDER,
self::TYPE_TOPIC, self::TYPE_SUBSCRIBER, and self::TYPE_MESSAGE; specifically
edit the ALL_RESOURCES definition to eliminate the <<<<<<<, =======, and >>>>>>>
lines and produce a clean array listing the full set of constants.
- Around line 74-81: The file contains unresolved git conflict markers; remove
the conflict markers (<<<<<<<, =======, >>>>>>>) and restore a single coherent
constants block that includes all required integration types by adding public
const TYPE_PLATFORM = 'platform'; public const TYPE_SUBSCRIBER = 'subscriber';
public const TYPE_MESSAGE = 'message'; (keep existing surrounding formatting and
visibility) so the class compiles cleanly and no markers remain.

In `@tests/Migration/Unit/Adapters/MockDestination.php`:
- Around line 54-61: Resolve the git merge conflict markers in
MockDestination::getSupportedResources(): remove the <<<<<<<, =======, and
>>>>>>> lines and return an array that includes both Resource::TYPE_PLATFORM and
the messaging types Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER, and Resource::TYPE_MESSAGE so all expected resource
types are present.

In `@tests/Migration/Unit/Adapters/MockSource.php`:
- Around line 83-90: Resolve the unresolved git conflict markers inside the
MockSource class's getSupportedResources() method by removing the <<<<<<<,
=======, >>>>>>> lines and returning a single array that includes
Resource::TYPE_PLATFORM plus Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER, and Resource::TYPE_MESSAGE so all supported resource
types are present.

---

Outside diff comments:
In `@src/Migration/Transfer.php`:
- Around line 61-95: This file still contains git conflict markers; remove the
markers (<<<<<<<, =======, >>>>>>>) and properly merge the two sides so all
intended resource group constants exist: keep GROUP_INTEGRATIONS_RESOURCES
(Resource::TYPE_PLATFORM) and also retain the new GROUP_DOCUMENTSDB_RESOURCES,
GROUP_VECTORSDB_RESOURCES, GROUP_DATABASES_RESOURCES and
GROUP_SETTINGS_RESOURCES definitions, ensuring there are no duplicate constant
names or syntax errors; also search for and remove conflict markers at the other
reported locations (around the blocks that include lines ~144, ~150 and
~410–418) so the PHP parses cleanly and all groups are preserved.

---

Nitpick comments:
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 2222-2231: The duplicate-detection using
$this->dbForPlatform->findOne(...) followed by an early return causes misleading
"Platform already exists" status under race conditions; remove the early return
and allow the subsequent createDocument call (and its DuplicateException
handler) to determine the final outcome, or change the early-path to only
short-circuit when you can guarantee exclusivity; specifically update the block
around $this->dbForPlatform->findOne, Query::equal(...) and
$resource->setStatus(Resource::STATUS_SKIPPED, ...) so it does not preemptively
set SKIPPED on a non-atomic check but instead relies on the existing
DuplicateException handling (the catch near the createDocument call) to set the
SKIPPED status for real duplicates caused by race conditions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0586d6ac-2bcc-411a-9dd6-aa0630e9e8d0

📥 Commits

Reviewing files that changed from the base of the PR and between 666f015 and 4939c67.

📒 Files selected for processing (11)
  • src/Migration/Destinations/Appwrite.php
  • src/Migration/Resource.php
  • src/Migration/Source.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Firebase.php
  • src/Migration/Sources/JSON.php
  • src/Migration/Sources/NHost.php
  • src/Migration/Transfer.php
  • tests/Migration/Unit/Adapters/MockDestination.php
  • tests/Migration/Unit/Adapters/MockSource.php

$this->dbForPlatform->createDocument('platforms', new UtopiaDocument([
'$id' => ID::unique(),
'$permissions' => $resource->getPermissions(),
'projectInternalId' => $this->dbForPlatform->getDocument('projects', $this->project)->getInternalId(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that getInternalId() is used correctly for project documents
ast-grep --pattern $'$_->getDocument($_, $_)->getInternalId()'
rg -n 'getInternalId\(\)' --type=php -C2

Repository: utopia-php/migration

Length of output: 768


🏁 Script executed:

# Check for Document class definition and getInternalId method
find . -type f -name "*.php" | xargs grep -l "class Document"

Repository: utopia-php/migration

Length of output: 162


🏁 Script executed:

# Search for getInternalId method definition
rg -n "function getInternalId|public.*getInternalId" --type=php

Repository: utopia-php/migration

Length of output: 46


🏁 Script executed:

# Check for getSequence usage to understand when it's used
rg -n "getSequence\(\)" --type=php -C2 | head -50

Repository: utopia-php/migration

Length of output: 2909


🏁 Script executed:

# Look at the dbForPlatform->getDocument usage in the same file
rg -n "getDocument\(" src/Migration/Destinations/Appwrite.php | head -20

Repository: utopia-php/migration

Length of output: 1112


🏁 Script executed:

# Read the Document class to verify available methods
cat -n src/Migration/Resources/Database/Document.php | head -100

Repository: utopia-php/migration

Length of output: 1712


🏁 Script executed:

# Check for all public methods in Document class
rg -n "public.*function" src/Migration/Resources/Database/Document.php

Repository: utopia-php/migration

Length of output: 170


🏁 Script executed:

# Read the Row class to check for getInternalId and other methods
cat -n src/Migration/Resources/Database/Row.php

Repository: utopia-php/migration

Length of output: 2991


🏁 Script executed:

# Read the Resource class to check for getInternalId and getSequence
cat -n src/Migration/Resource.php

Repository: utopia-php/migration

Length of output: 9627


🏁 Script executed:

# Check composer.json for dependencies
cat composer.json | grep -A 20 '"require"'

Repository: utopia-php/migration

Length of output: 599


🏁 Script executed:

# Search for dbForPlatform initialization and type hints to understand what it returns
rg -n "dbForPlatform" src/Migration/Destinations/Appwrite.php -B2 -A2 | head -40

Repository: utopia-php/migration

Length of output: 1552


🏁 Script executed:

# Search for other references to 'projects' document to understand its structure
rg -n "getDocument\('projects'" --type=php -A2 -B2

Repository: utopia-php/migration

Length of output: 604


🏁 Script executed:

# Search for getInternalId in the entire repo to understand its usage pattern
rg -n "getInternalId" --type=php -B3 -A3

Repository: utopia-php/migration

Length of output: 1505


🏁 Script executed:

# Check if there's a Project resource class that might have getInternalId
find . -type f -name "*.php" | xargs grep -l "class Project"

Repository: utopia-php/migration

Length of output: 48


🏁 Script executed:

# Verify the line context around 2240 to understand what method should be used
sed -n '2230,2250p' src/Migration/Destinations/Appwrite.php

Repository: utopia-php/migration

Length of output: 1026


🏁 Script executed:

# Check if utopia-php/database Document class has getInternalId method by examining any documentation or type hints
rg -n "import.*Document|use.*Document" src/Migration/Destinations/Appwrite.php

Repository: utopia-php/migration

Length of output: 292


🏁 Script executed:

# Check if there are any other patterns where we retrieve and use project document properties
rg -n "projects.*project\)" src/Migration/Destinations/Appwrite.php -A3 -B1 | head -50

Repository: utopia-php/migration

Length of output: 654


🏁 Script executed:

# Search for any documentation or comments about projectInternalId
rg -n "projectInternalId" --type=php -B3 -A3

Repository: utopia-php/migration

Length of output: 833


🏁 Script executed:

# Check the git history or comments around line 2240 to understand intent
git log -p --follow -S "projectInternalId" -- src/Migration/Destinations/Appwrite.php 2>/dev/null | head -100

Repository: utopia-php/migration

Length of output: 46


🏁 Script executed:

# Look at the broader context of the platforms document structure
rg -n "platforms.*createDocument" src/Migration/Destinations/Appwrite.php -B5 -A15

Repository: utopia-php/migration

Length of output: 46


🏁 Script executed:

# Check the imports to understand what's available from utopia-php/database
grep -r "use Utopia" src/Migration/Destinations/Appwrite.php | head -20

Repository: utopia-php/migration

Length of output: 944


🏁 Script executed:

# Look for any cached documents or other getDocument calls to see what methods are used on them
rg -n "getDocument\(" src/Migration/Destinations/Appwrite.php -A1 | grep -E "(getId|getSequence|getInternalId|get[A-Z])" | head -20

Repository: utopia-php/migration

Length of output: 1349


Replace getInternalId() with getSequence() on the project document.

The code calls getInternalId() which does not exist on Utopia\Database\Document. The correct method is getSequence(), as consistently used throughout the codebase for retrieving internal/sequential IDs from documents.

Line 2240
'projectInternalId' => $this->dbForPlatform->getDocument('projects', $this->project)->getSequence(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Destinations/Appwrite.php` at line 2240, The project document
access uses a non-existent method getInternalId(); replace that call with
getSequence() on the Document returned by
$this->dbForPlatform->getDocument('projects', $this->project) so the assignment
for 'projectInternalId' uses ->getSequence() instead of ->getInternalId();
update the expression where 'projectInternalId' is set (in the
Destinations\Appwrite migration code) to call getSequence().

Comment on lines +114 to +121
<<<<<<< HEAD
self::TYPE_PLATFORM,
=======
self::TYPE_PROVIDER,
self::TYPE_TOPIC,
self::TYPE_SUBSCRIBER,
self::TYPE_MESSAGE,
>>>>>>> origin/main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Second merge conflict in ALL_RESOURCES array.

The ALL_RESOURCES array also has unresolved merge conflict markers. Both the messaging types and TYPE_PLATFORM should be included.

🔧 Proposed fix to resolve the merge conflict
-<<<<<<< HEAD
-        self::TYPE_PLATFORM,
-=======
         self::TYPE_PROVIDER,
         self::TYPE_TOPIC,
         self::TYPE_SUBSCRIBER,
         self::TYPE_MESSAGE,
->>>>>>> origin/main
+        self::TYPE_PLATFORM,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<<<<<<< HEAD
self::TYPE_PLATFORM,
=======
self::TYPE_PROVIDER,
self::TYPE_TOPIC,
self::TYPE_SUBSCRIBER,
self::TYPE_MESSAGE,
>>>>>>> origin/main
self::TYPE_PROVIDER,
self::TYPE_TOPIC,
self::TYPE_SUBSCRIBER,
self::TYPE_MESSAGE,
self::TYPE_PLATFORM,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Resource.php` around lines 114 - 121, Remove the unresolved
merge conflict markers in the ALL_RESOURCES array inside the Resource class and
ensure the array contains all resource type constants: retain
self::TYPE_PLATFORM and add self::TYPE_PROVIDER, self::TYPE_TOPIC,
self::TYPE_SUBSCRIBER, and self::TYPE_MESSAGE; specifically edit the
ALL_RESOURCES definition to eliminate the <<<<<<<, =======, and >>>>>>> lines
and produce a clean array listing the full set of constants.

Comment on lines +54 to +61
<<<<<<< HEAD
Resource::TYPE_PLATFORM,
=======
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
>>>>>>> origin/main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unresolved merge conflict markers in test file.

The getSupportedResources() method contains git merge conflict markers that must be resolved. Both TYPE_PLATFORM and the messaging types should be included.

🔧 Proposed fix to resolve the merge conflict
             Resource::TYPE_MEMBERSHIP,
-<<<<<<< HEAD
-            Resource::TYPE_PLATFORM,
-=======
             Resource::TYPE_PROVIDER,
             Resource::TYPE_TOPIC,
             Resource::TYPE_SUBSCRIBER,
             Resource::TYPE_MESSAGE,
->>>>>>> origin/main
+            Resource::TYPE_PLATFORM,
         ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<<<<<<< HEAD
Resource::TYPE_PLATFORM,
=======
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
>>>>>>> origin/main
Resource::TYPE_MEMBERSHIP,
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
Resource::TYPE_PLATFORM,
];
🧰 Tools
🪛 GitHub Actions: CodeQL

[error] 54-54: PHPStan: Syntax error, unexpected T_SL, expecting ',' or ']' or ')' on line 54.

🪛 GitHub Actions: Tests

[error] 54-54: ParseError: syntax error, unexpected token '<<', expecting ']' (while running 'docker compose exec tests php vendor/bin/phpunit')

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Migration/Unit/Adapters/MockDestination.php` around lines 54 - 61,
Resolve the git merge conflict markers in
MockDestination::getSupportedResources(): remove the <<<<<<<, =======, and
>>>>>>> lines and return an array that includes both Resource::TYPE_PLATFORM and
the messaging types Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER, and Resource::TYPE_MESSAGE so all expected resource
types are present.

Comment on lines +83 to +90
<<<<<<< HEAD
Resource::TYPE_PLATFORM,
=======
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
>>>>>>> origin/main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unresolved merge conflict markers in test mock.

The getSupportedResources() method contains git merge conflict markers that must be resolved. Both TYPE_PLATFORM and the messaging types should be included.

🔧 Proposed fix to resolve the merge conflict
             Resource::TYPE_MEMBERSHIP,
-<<<<<<< HEAD
-            Resource::TYPE_PLATFORM,
-=======
             Resource::TYPE_PROVIDER,
             Resource::TYPE_TOPIC,
             Resource::TYPE_SUBSCRIBER,
             Resource::TYPE_MESSAGE,
->>>>>>> origin/main
+            Resource::TYPE_PLATFORM,
 
             // legacy
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<<<<<<< HEAD
Resource::TYPE_PLATFORM,
=======
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
>>>>>>> origin/main
Resource::TYPE_MEMBERSHIP,
Resource::TYPE_PROVIDER,
Resource::TYPE_TOPIC,
Resource::TYPE_SUBSCRIBER,
Resource::TYPE_MESSAGE,
Resource::TYPE_PLATFORM,
// legacy
🧰 Tools
🪛 GitHub Actions: CodeQL

[error] 83-83: PHPStan: Syntax error, unexpected T_SL, expecting ',' or ']' or ')' on line 83.

🪛 GitHub Actions: Tests

[error] 83-83: ParseError: syntax error, unexpected token '<<', expecting ']' (while running 'docker compose exec tests php vendor/bin/phpunit')

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Migration/Unit/Adapters/MockSource.php` around lines 83 - 90, Resolve
the unresolved git conflict markers inside the MockSource class's
getSupportedResources() method by removing the <<<<<<<, =======, >>>>>>> lines
and returning a single array that includes Resource::TYPE_PLATFORM plus
Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC, Resource::TYPE_SUBSCRIBER, and
Resource::TYPE_MESSAGE so all supported resource types are present.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Migration/Sources/Appwrite.php (2)

2235-2249: ⚠️ Potential issue | 🟠 Major

Initialize platform report and honor resourceIds filtering.

On Line 2240, returning early leaves \$report[Resource::TYPE_PLATFORM] unset. Also \$resourceIds is currently unused, so filtered reports can be incorrect.

💡 Suggested logic fix
 private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void
 {
-    if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
-        $consoleHeaders = $this->getConsoleHeaders();
+    if (!\in_array(Resource::TYPE_PLATFORM, $resources, true)) {
+        return;
+    }
+
+    $report[Resource::TYPE_PLATFORM] = 0;
+    $consoleHeaders = $this->getConsoleHeaders();

-        if ($consoleHeaders === null) {
-            return;
-        }
+    if ($consoleHeaders === null) {
+        return;
+    }

-        try {
-            $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
-            $report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0;
-        } catch (\Throwable) {
-            $report[Resource::TYPE_PLATFORM] = 0;
-        }
-    }
+    try {
+        $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
+        $platforms = $response['platforms'] ?? [];
+        if (!empty($resourceIds[Resource::TYPE_PLATFORM])) {
+            $allowed = \array_flip((array) $resourceIds[Resource::TYPE_PLATFORM]);
+            $platforms = \array_filter($platforms, fn (array $p): bool => isset($allowed[$p['$id'] ?? '']));
+            $report[Resource::TYPE_PLATFORM] = \count($platforms);
+            return;
+        }
+        $report[Resource::TYPE_PLATFORM] = $response['total'] ?? \count($platforms);
+    } catch (\Throwable) {
+        // keep fallback 0
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2235 - 2249, The
reportIntegrations method currently skips setting
$report[Resource::TYPE_PLATFORM] when getConsoleHeaders() returns null and never
respects the $resourceIds filter; update reportIntegrations to always initialize
$report[Resource::TYPE_PLATFORM] (e.g., to 0) before the headers check and, when
$resourceIds is non-empty, only populate the platform count if
Resource::TYPE_PLATFORM is included in $resourceIds (otherwise leave it as 0 or
skip); keep using getConsoleHeaders() and the existing call('GET',
'/projects/'.$this->project.'/platforms', $consoleHeaders) inside the try/catch
to set the count and preserve the catch behavior for exceptions.

2277-2284: ⚠️ Potential issue | 🟠 Major

Use paginated export for platforms and thread batchSize through.

exportGroupIntegrations() accepts \$batchSize, but exportPlatforms() currently performs a single non-paginated request and does not apply root-resource filtering. This can truncate exported platforms.

📌 Directional refactor
 protected function exportGroupIntegrations(int $batchSize, array $resources): void
 {
-    if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
+    if (\in_array(Resource::TYPE_PLATFORM, $resources, true)) {
         $this->exportWithConsoleHeaders(
             Resource::TYPE_PLATFORM,
             Transfer::GROUP_INTEGRATIONS,
-            $this->exportPlatforms(...)
+            fn (array $consoleHeaders) => $this->exportPlatforms($consoleHeaders, $batchSize)
         );
     }
 }

-private function exportPlatforms(array $consoleHeaders): void
+private function exportPlatforms(array $consoleHeaders, int $batchSize): void
 {
-    $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
-    ...
+    // paginate with limit + cursorAfter until page size < $batchSize
+    // and respect rootResourceId/rootResourceType when TYPE_PLATFORM is targeted
 }
For Appwrite REST API v1 / PHP SDK v19, confirm whether `GET /projects/{projectId}/platforms` supports pagination (`limit`, `cursorAfter`) via `queries`, and provide the exact request/query format.

Also applies to: 2343-2367

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2277 - 2284, The
exportPlatforms call inside exportGroupIntegrations must be converted to a
paginated exporter that respects the passed $batchSize and filters by root
resource: update exportPlatforms to accept (int $batchSize, ?string
$rootResource = null) and implement paginated requests to Appwrite platforms
using the SDK's queries (e.g. include limit=$batchSize and cursorAfter handling)
so each page is yielded/exported; then change the exportGroupIntegrations
invocation to pass $batchSize and the appropriate root resource when calling
$this->exportPlatforms(...) so Transfer::GROUP_INTEGRATIONS export will iterate
all pages rather than a single non-paginated request.
🤖 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/Migration/Sources/Appwrite.php`:
- Around line 2235-2250: The file is missing closing braces for three methods
causing a parse error; add the missing closing brace "}" at the end of
reportIntegrations (method reportIntegrations), add the missing closing brace at
the end of exportWithConsoleHeaders (method exportWithConsoleHeaders), and add
the missing closing brace at the end of exportPlatforms (method
exportPlatforms). Also move any docblocks currently injected after those method
bodies (the malformed "@param string $databaseType" and other docblocks) back
above their respective function declarations or remove them so they are not
placed where the closing brace belongs, ensuring each method ends with a proper
'}' before the next docblock or method declaration.

---

Duplicate comments:
In `@src/Migration/Sources/Appwrite.php`:
- Around line 2235-2249: The reportIntegrations method currently skips setting
$report[Resource::TYPE_PLATFORM] when getConsoleHeaders() returns null and never
respects the $resourceIds filter; update reportIntegrations to always initialize
$report[Resource::TYPE_PLATFORM] (e.g., to 0) before the headers check and, when
$resourceIds is non-empty, only populate the platform count if
Resource::TYPE_PLATFORM is included in $resourceIds (otherwise leave it as 0 or
skip); keep using getConsoleHeaders() and the existing call('GET',
'/projects/'.$this->project.'/platforms', $consoleHeaders) inside the try/catch
to set the count and preserve the catch behavior for exceptions.
- Around line 2277-2284: The exportPlatforms call inside exportGroupIntegrations
must be converted to a paginated exporter that respects the passed $batchSize
and filters by root resource: update exportPlatforms to accept (int $batchSize,
?string $rootResource = null) and implement paginated requests to Appwrite
platforms using the SDK's queries (e.g. include limit=$batchSize and cursorAfter
handling) so each page is yielded/exported; then change the
exportGroupIntegrations invocation to pass $batchSize and the appropriate root
resource when calling $this->exportPlatforms(...) so
Transfer::GROUP_INTEGRATIONS export will iterate all pages rather than a single
non-paginated request.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceee9396-ec71-4744-abb8-16c3f8f4611f

📥 Commits

Reviewing files that changed from the base of the PR and between 4939c67 and 12a4d8e.

📒 Files selected for processing (5)
  • src/Migration/Resource.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Transfer.php
  • tests/Migration/Unit/Adapters/MockDestination.php
  • tests/Migration/Unit/Adapters/MockSource.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Migration/Resource.php

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Migration/Sources/Appwrite.php (2)

2235-2251: ⚠️ Potential issue | 🟠 Major

Initialize platform report count and honor resourceIds in integrations report.

reportIntegrations() can return without setting \$report[Resource::TYPE_PLATFORM], and filtered reports ignore \$resourceIds, which can produce incorrect totals.

💡 Proposed fix
 private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void
 {
-    if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
-        $consoleHeaders = $this->getConsoleHeaders();
-
-        if ($consoleHeaders === null) {
-            return;
-        }
-
-        try {
-            $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
-            $report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0;
-        } catch (\Throwable) {
-            $report[Resource::TYPE_PLATFORM] = 0;
-        }
+    if (!\in_array(Resource::TYPE_PLATFORM, $resources, true)) {
+        return;
+    }
+
+    $report[Resource::TYPE_PLATFORM] = 0;
+    $consoleHeaders = $this->getConsoleHeaders();
+    if ($consoleHeaders === null) {
+        return;
+    }
+
+    try {
+        $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
+        $platforms = $response['platforms'] ?? [];
+
+        if (!empty($resourceIds[Resource::TYPE_PLATFORM])) {
+            $allowed = \array_flip((array) $resourceIds[Resource::TYPE_PLATFORM]);
+            $platforms = \array_filter(
+                $platforms,
+                fn (array $platform): bool => isset($allowed[$platform['$id'] ?? ''])
+            );
+            $report[Resource::TYPE_PLATFORM] = \count($platforms);
+            return;
+        }
+
+        $report[Resource::TYPE_PLATFORM] = $response['total'] ?? \count($platforms);
+    } catch (\Throwable) {
+        // keep fallback 0
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2235 - 2251, Initialize
$report[Resource::TYPE_PLATFORM] = 0 at the start of reportIntegrations(), and
when fetching platforms via call('GET', '/projects/' . $this->project .
'/platforms', $consoleHeaders) honor the $resourceIds filter: if $resourceIds is
non-empty, inspect $response['data'] (the returned platform items) and set
$report[Resource::TYPE_PLATFORM] to the count of items whose '$id' (or 'id') is
in $resourceIds; otherwise fall back to $response['total'] ?? 0. Keep the
existing try/catch and ensure the catch leaves $report[Resource::TYPE_PLATFORM]
as 0.

2281-2290: ⚠️ Potential issue | 🟠 Major

Use \$batchSize and paginate platform export (current flow can truncate data).

exportGroupIntegrations() receives \$batchSize, but exportPlatforms() performs a single list call with no cursor loop and no root-platform filter handling. This can export only the first page and ignore targeted root exports.

💡 Proposed fix
 protected function exportGroupIntegrations(int $batchSize, array $resources): void
 {
     if (\in_array(Resource::TYPE_PLATFORM, $resources)) {
         $this->exportWithConsoleHeaders(
             Resource::TYPE_PLATFORM,
             Transfer::GROUP_INTEGRATIONS,
-            $this->exportPlatforms(...)
+            fn (array $consoleHeaders) => $this->exportPlatforms($consoleHeaders, $batchSize)
         );
     }
 }

 /**
  * `@throws` AppwriteException
  */
-private function exportPlatforms(array $consoleHeaders): void
+private function exportPlatforms(array $consoleHeaders, int $batchSize): void
 {
-    $response = $this->call('GET', '/projects/' . $this->project . '/platforms', $consoleHeaders);
-
-    if (empty($response['platforms'])) {
-        return;
-    }
-
-    $platforms = [];
-
-    foreach ($response['platforms'] as $platform) {
-        $platforms[] = new Platform(
-            $platform['$id'] ?? '',
-            $platform['type'] ?? '',
-            $platform['name'] ?? '',
-            $platform['key'] ?? '',
-            $platform['store'] ?? '',
-            $platform['hostname'] ?? '',
-            createdAt: $platform['$createdAt'] ?? '',
-            updatedAt: $platform['$updatedAt'] ?? '',
-        );
-    }
-
-    $this->callback($platforms);
+    $cursor = null;
+    while (true) {
+        $queries = [Query::limit($batchSize)];
+
+        if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_PLATFORM) {
+            $queries[] = Query::equal('$id', $this->rootResourceId);
+            $queries[] = Query::limit(1);
+        } elseif ($cursor !== null) {
+            $queries[] = Query::cursorAfter($cursor);
+        }
+
+        $response = $this->call(
+            'GET',
+            '/projects/' . $this->project . '/platforms',
+            $consoleHeaders,
+            ['queries' => $queries]
+        );
+
+        $current = $response['platforms'] ?? [];
+        if (empty($current)) {
+            break;
+        }
+
+        $platforms = [];
+        foreach ($current as $platform) {
+            $platforms[] = new Platform(
+                $platform['$id'] ?? '',
+                $platform['type'] ?? '',
+                $platform['name'] ?? '',
+                $platform['key'] ?? '',
+                $platform['store'] ?? '',
+                $platform['hostname'] ?? '',
+                createdAt: $platform['$createdAt'] ?? '',
+                updatedAt: $platform['$updatedAt'] ?? '',
+            );
+        }
+
+        $this->callback($platforms);
+
+        if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_PLATFORM) {
+            break;
+        }
+
+        $cursor = $current[\count($current) - 1]['$id'] ?? null;
+        if (\count($current) < $batchSize) {
+            break;
+        }
+    }
 }
For Appwrite API/SDK v19, what is the exact supported pagination format for GET /v1/projects/{projectId}/platforms (queries[] with limit/cursorAfter), and how should those queries be encoded for REST requests?

Also applies to: 2351-2375

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Appwrite.php` around lines 2281 - 2290,
exportGroupIntegrations currently passes $batchSize but exportPlatforms does a
single list call; update exportPlatforms to loop using $batchSize and Appwrite
v19 cursor pagination: call listPlatforms repeatedly with queries[] params
"limit(<batchSize>)" and "cursorAfter(<lastId>)" (encoded as
queries[]=limit(<n>)&queries[]=cursorAfter(<id>) in REST) until no more results,
and honor any root-platform filter when building the queries; reference the
exportGroupIntegrations and exportPlatforms methods and replace the single-call
list with a cursor-based loop that updates cursorAfter from the last item each
iteration.
🤖 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/Migration/Destinations/Appwrite.php`:
- Around line 2202-2216: The switch in importIntegrationsResource(Resource
$resource) only handles Resource::TYPE_PLATFORM and then unconditionally sets
status to STATUS_SUCCESS if not STATUS_SKIPPED, allowing unsupported types to be
marked success; add a default branch (or explicit check after the switch) that
fails fast for unknown resource names by setting the resource to a failure
status (or throwing an exception) and/or logging an error, referencing
importIntegrationsResource(), Resource::TYPE_PLATFORM, Resource::STATUS_SKIPPED
and Resource::STATUS_SUCCESS so unsupported integration types are never silently
marked successful.

---

Duplicate comments:
In `@src/Migration/Sources/Appwrite.php`:
- Around line 2235-2251: Initialize $report[Resource::TYPE_PLATFORM] = 0 at the
start of reportIntegrations(), and when fetching platforms via call('GET',
'/projects/' . $this->project . '/platforms', $consoleHeaders) honor the
$resourceIds filter: if $resourceIds is non-empty, inspect $response['data']
(the returned platform items) and set $report[Resource::TYPE_PLATFORM] to the
count of items whose '$id' (or 'id') is in $resourceIds; otherwise fall back to
$response['total'] ?? 0. Keep the existing try/catch and ensure the catch leaves
$report[Resource::TYPE_PLATFORM] as 0.
- Around line 2281-2290: exportGroupIntegrations currently passes $batchSize but
exportPlatforms does a single list call; update exportPlatforms to loop using
$batchSize and Appwrite v19 cursor pagination: call listPlatforms repeatedly
with queries[] params "limit(<batchSize>)" and "cursorAfter(<lastId>)" (encoded
as queries[]=limit(<n>)&queries[]=cursorAfter(<id>) in REST) until no more
results, and honor any root-platform filter when building the queries; reference
the exportGroupIntegrations and exportPlatforms methods and replace the
single-call list with a cursor-based loop that updates cursorAfter from the last
item each iteration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe7b7011-99c4-442c-a44c-5df899e85d2f

📥 Commits

Reviewing files that changed from the base of the PR and between 12a4d8e and 8684335.

📒 Files selected for processing (2)
  • src/Migration/Destinations/Appwrite.php
  • src/Migration/Sources/Appwrite.php

Comment on lines +2202 to +2216
public function importIntegrationsResource(Resource $resource): Resource
{
switch ($resource->getName()) {
case Resource::TYPE_PLATFORM:
/** @var Platform $resource */
$this->createPlatform($resource);
break;
}

if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
$resource->setStatus(Resource::STATUS_SUCCESS);
}

return $resource;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast on unsupported integration resource types.

importIntegrationsResource() has no default branch, so unknown integration types can be marked STATUS_SUCCESS without any import action.

💡 Proposed fix
 public function importIntegrationsResource(Resource $resource): Resource
 {
     switch ($resource->getName()) {
         case Resource::TYPE_PLATFORM:
             /** `@var` Platform $resource */
             $this->createPlatform($resource);
             break;
+        default:
+            throw new \Exception('Unknown integrations resource type: ' . $resource->getName(), Exception::CODE_VALIDATION);
     }

     if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
         $resource->setStatus(Resource::STATUS_SUCCESS);
     }

     return $resource;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Destinations/Appwrite.php` around lines 2202 - 2216, The switch
in importIntegrationsResource(Resource $resource) only handles
Resource::TYPE_PLATFORM and then unconditionally sets status to STATUS_SUCCESS
if not STATUS_SKIPPED, allowing unsupported types to be marked success; add a
default branch (or explicit check after the switch) that fails fast for unknown
resource names by setting the resource to a failure status (or throwing an
exception) and/or logging an error, referencing importIntegrationsResource(),
Resource::TYPE_PLATFORM, Resource::STATUS_SKIPPED and Resource::STATUS_SUCCESS
so unsupported integration types are never silently marked successful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant