Feat docker geo by lohanidamodar · Pull Request #10824 · appwrite/appwrite · GitHub
Skip to content

Feat docker geo#10824

Open
lohanidamodar wants to merge 106 commits into
mainfrom
feat-docker-geo-18x
Open

Feat docker geo#10824
lohanidamodar wants to merge 106 commits into
mainfrom
feat-docker-geo-18x

Conversation

@lohanidamodar

Copy link
Copy Markdown
Member

What does this PR do?

  • Add docker geo db container
  • Use Geo DB from docker container

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)

Related PRs and Issues

  • (Related PR or issue)

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@lohanidamodar lohanidamodar mentioned this pull request Nov 17, 2025
2 tasks
@coderabbitai

coderabbitai Bot commented Nov 17, 2025

Copy link
Copy Markdown
Contributor

@pkg-pr-new

pkg-pr-new Bot commented Nov 17, 2025

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@10824

commit: 59eae60

@github-actions

github-actions Bot commented Nov 17, 2025

Copy link
Copy Markdown

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libecpg 18.1-r0 CVE-2026-2004 HIGH
libecpg 18.1-r0 CVE-2026-2005 HIGH
libecpg 18.1-r0 CVE-2026-2006 HIGH
libecpg 18.1-r0 CVE-2026-2007 HIGH
libecpg-dev 18.1-r0 CVE-2026-2004 HIGH
libecpg-dev 18.1-r0 CVE-2026-2005 HIGH
libecpg-dev 18.1-r0 CVE-2026-2006 HIGH
libecpg-dev 18.1-r0 CVE-2026-2007 HIGH
libpq 18.1-r0 CVE-2026-2004 HIGH
libpq 18.1-r0 CVE-2026-2005 HIGH
libpq 18.1-r0 CVE-2026-2006 HIGH
libpq 18.1-r0 CVE-2026-2007 HIGH
libpq-dev 18.1-r0 CVE-2026-2004 HIGH
libpq-dev 18.1-r0 CVE-2026-2005 HIGH
libpq-dev 18.1-r0 CVE-2026-2006 HIGH
libpq-dev 18.1-r0 CVE-2026-2007 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2004 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2005 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2006 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2007 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

@lohanidamodar lohanidamodar changed the title Feat docker geo 18x Feat docker geo Nov 17, 2025
@lohanidamodar lohanidamodar changed the base branch from main to 1.8.x November 17, 2025 01:22
@github-actions

github-actions Bot commented Nov 17, 2025

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → feat-docker-geo-18x (after).

Metric Before After Change
🚀 Requests/sec 125.48 118.18 🔴 -5.8%
⏱️ Latency P50 109.41 ms 117.63 ms 🔴 +7.5%
⏱️ Latency P95 426.27 ms 438.38 ms +2.8%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 117.63 438.38 7,752 118.18 +12.11
Account 206.94 544.89 408 6.76 -3.95
TablesDB 106.85 393.5 4,216 65.79 +29.14
Storage 107.87 443.3 2,040 33.48 -22.21
Functions 178.07 509.26 1,088 18.71 -9.59

Top API waits (after)

API request Max wait (ms)
storage.buckets.create 1,301.57
account.get 1,198.62
tablesdb.rows.update 1,180.53
tablesdb.rows.delete 1,114.37
storage.files.get 1,102.76

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

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

⚠️ Outside diff range comments (13)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)

87-92: Remove the hard-coded “Temp fix for logs”.

These static date filters will hide most logs and are already stale (Feb 2025). Gate behind a config flag or remove entirely.

Apply this diff to remove them:

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)

82-86: Delete the stale “Temp fix for logs” block.

Static date window hides logs and should not ship.

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)

101-105: Remove the hard-coded logs filter.

Same stale date window; remove or gate via config.

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);
app/controllers/api/users.php (1)

929-934: Drop the temporary logs date filter.

Prevents expected logs from appearing; please remove or guard by env.

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);
app/controllers/api/teams.php (3)

475-483: Roles whitelist isn’t applied (array_filter result ignored) + stray semicolon.

array_filter doesn’t assign back to $roles; whitelist isn’t enforced for console. Remove the stray semicolon and apply the filter result.

-        if ($project->getId() === 'console') {
-            ;
+        if ($project->getId() === 'console') {
             $roles = array_keys(Config::getParam('roles', []));
-            array_filter($roles, function ($role) {
-                return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
-            });
+            $roles = array_values(array_filter($roles, function ($role) {
+                return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS], true);
+            }));
             return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
         }

1086-1095: Roles whitelist isn’t applied in updateMembership.

Same bug here; enforce the filtered list.

         if ($project->getId() === 'console') {
             $roles = array_keys(Config::getParam('roles', []));
-            array_filter($roles, function ($role) {
-                return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
-            });
+            $roles = array_values(array_filter($roles, function ($role) {
+                return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS], true);
+            }));
             return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
         }

1468-1472: Remove hard-coded “Temp fix for logs”.

Date-gated OR filter skews results. Delete it or feature-gate behind a config flag.

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);
app/controllers/api/account.php (6)

2913-2917: Remove hard-coded “Temp fix for logs”.

Skews account logs. Delete or gate by config.

-        // Temp fix for logs
-        $queries[] = Query::or([
-            Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
-            Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
-        ]);

2189-2195: Email template device var duplicates brand; fallback to model.

-            'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
+            'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',

2461-2468: OTP email: device var duplicates brand; fallback to model.

-            'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
+            'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',

4931-4934: MFA challenge email: device var duplicates brand; fallback to model.

-                    'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
+                    'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',

656-665: Event sessionId set to last iterated session, not the current one.

Move setParam into the matching branch and drop the post-loop call.

                 if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) {
                     $session->setAttribute('current', true);
@@
-                    // Use current session for events.
-                    $queueForEvents
-                        ->setPayload($response->output($session, Response::MODEL_SESSION));
+                    // Use current session for events.
+                    $queueForEvents
+                        ->setParam('userId', $user->getId())
+                        ->setParam('sessionId', $session->getId())
+                        ->setPayload($response->output($session, Response::MODEL_SESSION));
@@
-        $dbForProject->purgeCachedDocument('users', $user->getId());
-
-        $queueForEvents
-            ->setParam('userId', $user->getId())
-            ->setParam('sessionId', $session->getId());
+        $dbForProject->purgeCachedDocument('users', $user->getId());

Also applies to: 669-672


156-161: Year format token should be 'Y', not 'YYYY'.

PHP DateTime uses single-letter tokens, and 'Y' is the correct token for a 4-digit year; 'YYYY' is not recognized and will be treated as literal text.

-        'year' => (new \DateTime())->format('YYYY'),
+        'year' => (new \DateTime())->format('Y'),
♻️ Duplicate comments (3)
app/controllers/api/messaging.php (3)

2543-2545: Same geoRecord concerns as provider logs

This topic-logs endpoint uses geoRecord in the same way as the provider logs (countryCode/countryName taken from a single per-request geoRecord and applied to all log rows, with direct array indexing). The same semantic and null-safety issues apply here as in the /providers/:providerId/logs comment.

Also applies to: 2601-2602


2959-2961: Same geoRecord concerns as provider logs

Subscriber logs also inject a single geoRecord and reuse it for all log entries, with direct array indexing. This has the same behavior and robustness issues described in the provider logs comment.

Also applies to: 3017-3018


3752-3754: Same geoRecord concerns as provider logs

Message logs likewise apply one per-request geoRecord to every log entry and access countryCode/countryName without null-coalescing. Same semantic and null-safety issues as noted for provider logs.

Also applies to: 3810-3811

🧹 Nitpick comments (11)
docker-compose.yml (1)

965-971: Consider production-readiness configurations for the geo service.

The new appwrite-geo service is using version 0.1.0 and lacks several configurations present in other services:

  1. No restart policy: Other services use restart: unless-stopped for resilience
  2. No health checks: Unable to detect if the service is functioning properly
  3. No resource limits: Could consume unlimited resources
  4. Version 0.1.0: Very early version - verify this is production-ready
  5. Missing dependency: The main appwrite service doesn't list appwrite-geo in depends_on

Consider applying this configuration pattern consistent with other services:

   appwrite-geo:
     container_name: appwrite-geo
     image: appwrite/geo:0.1.0
+    restart: unless-stopped
+    <<: *x-logging
     networks:
       - appwrite
     environment:
       - GEO_SECRET=$_APP_GEO_SECRET

Also, add the geo service as a dependency where needed:

# In the appwrite service
depends_on:
  - mariadb
  - redis
  - appwrite-geo
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)

77-77: Remove unused $locale parameter.

The $locale parameter is injected but never used in the action method. This adds unnecessary overhead to the dependency injection system.

Remove the unused injection:

             ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
             ->inject('response')
             ->inject('dbForProject')
-            ->inject('locale')
             ->inject('geoRecord')
             ->callback($this->action(...));

And update the method signature:

-    public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, array $geoRecord): void
+    public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, array $geoRecord): void
app/init/resources.php (1)

717-741: Back-compat: expose countryName and safe defaults.

Downstream code expects countryName. Add alias and guard absent fields.

     $output = [];
     $output['ip'] = $ip;
@@
-    if (!empty($record)) {
-        $output['countryCode'] = $record['countryCode'];
+    if (!empty($record)) {
+        $output['countryCode'] = $record['countryCode'] ?? '--';
         $output['country'] = $locale->getText('countries.' . strtolower($record['countryCode']), $locale->getText('locale.country.unknown'));
         $output['continent'] = $locale->getText('continents.' . strtolower($record['continentCode']), $locale->getText('locale.country.unknown'));
-        $output['continentCode'] = $record['continentCode'];
-        $output['eu'] = (\in_array($record['countryCode'], $eu)) ? true : false;
+        $output['continentCode'] = $record['continentCode'] ?? '--';
+        $output['eu'] = (\in_array($record['countryCode'] ?? '', $eu)) ? true : false;
@@
-        $output['currency'] = $currency;
+        $output['currency'] = $currency;
+        // Alias for existing consumers
+        $output['countryName'] = $output['country'];
     } else {
         $output['countryCode'] = '--';
         $output['country'] = $locale->getText('locale.country.unknown');
         $output['continent'] = $locale->getText('locale.country.unknown');
         $output['continentCode'] = '--';
         $output['eu'] = false;
-        $output['currency'] = $currency;
+        $output['currency'] = $currency;
+        $output['countryName'] = $output['country'];
     }
app/controllers/shared/api/auth.php (1)

39-45: Clarify denylist condition and normalize country codes.

Use explicit checks and trim codes. Also guard missing geoRecord key.

-        $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
-        if (!empty($denylist && $project->getId() === 'console')) {
-            $countries = explode(',', $denylist);
-            $country = $geoRecord['countryCode'] ?? '';
+        $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
+        if ($project->getId() === 'console' && $denylist !== '') {
+            $countries = array_map(static fn($c) => strtoupper(trim($c)), explode(',', $denylist));
+            $country = strtoupper($geoRecord['countryCode'] ?? '');
             if (in_array($country, $countries)) {
                 throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED);
             }
         }
app/controllers/api/users.php (1)

2212-2215: Guard session countryCode assignment.

Minor: add default to avoid notices if geoRecord is missing fields (defensive coding).

-                'countryCode' => $geoRecord['countryCode'],
+                'countryCode' => $geoRecord['countryCode'] ?? '--',
app/controllers/api/locale.php (1)

34-36: Expose countryName for callers relying on it.

Returning geoRecord is fine, but many call sites expect countryName. Consider adding it before responding.

-        $output = $geoRecord;
+        $output = $geoRecord;
+        $output['countryName'] = $output['country'] ?? $output['countryName'] ?? null;
app/controllers/general.php (3)

59-60: Remove unused $utopia param from router signature.

Not used inside router(). Drop it or prefix with _ to satisfy linters.

-function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, array $geoRecord, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
+function router(Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, array $geoRecord, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)

Note: Update all call sites accordingly.


380-387: Use geoRecord['eu'] directly.

Slight simplification; avoids re-checking against config.

-            if ($geoRecord) {
-                $eu = Config::getParam('locale-eu');
-
-                $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? '';
-                $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? '';
-                $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false';
-            }
+            if ($geoRecord) {
+                $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? '';
+                $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? '';
+                $headers['x-appwrite-continent-eu'] = ($geoRecord['eu'] ?? false) ? 'true' : 'false';
+            }

868-875: Lazy-inject geoRecord to avoid external calls on init/OPTIONS/robots/humans requests.

Verification confirms geoRecord is injected in broad paths but underutilized:

  • Line 852 (App::init() for api/web groups): Passed to all downstream handlers regardless of need
  • Line 1123 (App::options()): Only used conditionally in router() calls
  • Line 1441 (/robots.txt): Only needed in else branch for router(); main path renders template without it
  • Line 1475 (/humans.txt): Only needed in else branch for router(); main path renders template without it

Move geoRecord injection into the router path only, or refactor to resolve it lazily within router() instead of at the entry points.

app/controllers/api/teams.php (1)

1512-1514: Per-log geo should not use the viewer’s geoRecord; also add null-coalescing.

  • Use stored per-log geo (if available) or derive from each log’s IP; geoRecord reflects the current caller.
  • Add safe defaults to avoid notices.
-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? '';
+            $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';

If per-log geo is desired, confirm product intent; we can wire a lookup using $log['ip'] against the geo service.

app/controllers/api/account.php (1)

2937-2939: Per-log geo should not use the viewer’s geoRecord; also add null-coalescing.

Use stored per-log geo or derive from each log’s IP; add safe defaults meanwhile.

-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? '';
+            $output[$i]['countryName'] = $geoRecord['countryName'] ?? '',
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between deb5039 and cc05de0.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .env (1 hunks)
  • app/config/collections/common.php (1 hunks)
  • app/controllers/api/account.php (13 hunks)
  • app/controllers/api/locale.php (1 hunks)
  • app/controllers/api/messaging.php (8 hunks)
  • app/controllers/api/teams.php (4 hunks)
  • app/controllers/api/users.php (4 hunks)
  • app/controllers/general.php (9 hunks)
  • app/controllers/shared/api/auth.php (1 hunks)
  • app/init/resources.php (2 hunks)
  • composer.json (1 hunks)
  • docker-compose.yml (1 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1 hunks)
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-08T01:20:14.364Z
Learnt from: stnguyen90
Repo: appwrite/appwrite PR: 10119
File: app/controllers/api/account.php:1226-1232
Timestamp: 2025-07-08T01:20:14.364Z
Learning: In Appwrite, `_APP_DOMAIN` is a required environment variable that must always be set for the system to function properly.

Applied to files:

  • .env
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.

Applied to files:

  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
🧬 Code graph analysis (12)
app/controllers/shared/api/auth.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (77-151)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
  • action (73-138)
app/controllers/api/locale.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (78-156)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (78-156)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (77-151)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)
  • action (68-133)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (78-156)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (77-151)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
  • action (73-138)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
app/controllers/api/teams.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (78-156)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
  • action (73-138)
app/init/resources.php (1)
tests/e2e/Client.php (2)
  • Client (8-342)
  • addHeader (161-166)
app/controllers/api/messaging.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
app/controllers/api/account.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
app/controllers/api/users.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
app/controllers/general.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (78-156)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (77-151)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
  • action (73-138)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (99-506)
🪛 dotenv-linter (4.0.0)
.env

[warning] 126-126: [UnorderedKey] The _APP_STATS_USAGE_DUAL_WRITING_DBS key should go before the _APP_STORAGE_ANTIVIRUS key

(UnorderedKey)


[warning] 127-127: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 127-127: [UnorderedKey] The _APP_GEO_SECRET key should go before the _APP_GRAPHQL_MAX_BATCH_SIZE key

(UnorderedKey)

🪛 GitHub Actions: Tests
app/init/resources.php

[error] 685-685: Server error occurring during account creation (POST: /v1/account). HTTP 500 - general_unknown.

🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php

78-78: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php

73-73: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php

68-68: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php

77-77: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

app/controllers/general.php

59-59: Avoid unused parameters such as '$utopia'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
🔇 Additional comments (4)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1)

53-53: LGTM! Dependency injection updated consistently.

The change from inject('geodb') to inject('geoRecord') aligns with the broader migration to array-based geolocation data.

src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1)

52-52: LGTM! Consistent with the geolocation refactoring.

The dependency injection change is correct and aligns with the migration pattern.

composer.json (1)

61-61: Dependency is reproducible due to composer.lock pinning.

The composer.lock file pins utopia-php/fetch to a specific commit (570209894551027b28375024ceae87ed39c24b40), which ensures reproducible builds regardless of the branch name specified in composer.json. This is the standard Composer workflow for feature branches and development versions.

While using a stable release would be preferable once available, the current setup with a pinned commit eliminates the reproducibility and stability risks initially flagged. No action is required.

app/init/resources.php (1)

690-699: I'll search the codebase to find information about the Utopia library and how it's being used.

Based on my research, I found the official Utopia/Fetch library documentation. The Client class supports setConnectionTimeout() and setTimeout() methods for configuration, and the Response object has a json() method to decode response body.

However, the review comment contains some inaccuracies:

  1. setBaseUrl() does not exist – the fetch() method accepts url and method as direct parameters, not through a setBaseUrl() call
  2. Method name is setConnectionTimeout() – not "setConnectTimeout()" as suggested in the comment
  3. HTTP methods are strings – the method parameter is a string (like 'GET' or 'POST'), not a Client constant

Update the geo service client configuration to add timeouts and encode the IP parameter properly.

The Utopia\Fetch\Client API differs from the review suggestion:

  • Use fetch(url: ..., method: ...) to pass the URL directly as a parameter instead of setBaseUrl()
  • The correct method name is setConnectionTimeout() (not setConnectTimeout)
  • HTTP methods are strings ('GET', 'POST', etc.), not constants
  • The env var for the endpoint should still be env-configurable at fetch time
    $client = new Client();
    $client->addHeader('Authorization', 'Bearer ' . System::getEnv('_APP_GEO_SECRET', ''));
+   $client->setConnectionTimeout(1000)->setTimeout(1500);
    
-   $client->setBaseUrl('http://appwrite-geo/v1');
+   $endpoint = System::getEnv('_APP_GEO_ENDPOINT', 'http://appwrite-geo/v1');
    
    $record = null;
    try {
-       $record = $client->fetch("/ips/{$ip}", Client::METHOD_GET);
+       $record = $client->fetch($endpoint . '/ips/' . rawurlencode((string)$ip), 'GET');
        $record = $record->json();

Comment thread .env Outdated
Comment thread app/config/collections/common.php
Comment thread app/controllers/api/account.php Outdated
Comment thread app/controllers/api/account.php Outdated
Comment thread app/controllers/api/account.php Outdated
Comment thread src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php Outdated
Comment thread src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php Outdated
Comment thread src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php Outdated
Comment thread src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
docker-compose.yml (1)

966-972: Add restart policy and logging configuration for consistency.

The appwrite-geo service is missing a restart policy and logging configuration that other services in the stack use. For consistency and reliability, consider adding:

  1. restart: unless-stopped (similar to other services like appwrite-console at line 224)
  2. <<: *x-logging (for consistent log rotation/management like other services)

Apply this diff:

   appwrite-geo:
+    <<: *x-logging
     container_name: appwrite-geo
     image: appwrite/geo:0.1.0
+    restart: unless-stopped
     networks:
       - appwrite
     environment:
       - GEO_SECRET=$_APP_GEO_SECRET
app/init/resources.php (1)

686-694: Add timeout configuration and consider making the base URL configurable.

The HTTP client lacks timeout configuration, which could cause requests to hang indefinitely if the geo service is slow or unresponsive. Additionally, the base URL is hardcoded to http://appwrite-geo/v1, which works for Docker Compose but reduces flexibility.

Consider these improvements:

  1. Add timeout configuration to the Client
  2. Make the base URL configurable via environment variable

Example:

$client = new Client();
$client->addHeader('Authorization', 'Bearer ' . System::getEnv('_APP_GEO_SECRET'));
$client->setTimeout(5); // 5 second timeout
$client->setBaseUrl(System::getEnv('_APP_GEO_URL', 'http://appwrite-geo/v1'));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc05de0 and cb4f1dc.

📒 Files selected for processing (2)
  • app/init/resources.php (3 hunks)
  • docker-compose.yml (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-08T01:20:14.364Z
Learnt from: stnguyen90
Repo: appwrite/appwrite PR: 10119
File: app/controllers/api/account.php:1226-1232
Timestamp: 2025-07-08T01:20:14.364Z
Learning: In Appwrite, `_APP_DOMAIN` is a required environment variable that must always be set for the system to function properly.

Applied to files:

  • docker-compose.yml
🧬 Code graph analysis (1)
app/init/resources.php (1)
src/Appwrite/Extend/Exception.php (1)
  • Exception (7-464)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: E2E Service Test (Users)
  • GitHub Check: E2E Service Test (Migrations)
  • GitHub Check: E2E Service Test (Teams)
  • GitHub Check: E2E Service Test (Realtime)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (Sites)
  • GitHub Check: E2E Service Test (Projects)
  • GitHub Check: E2E Service Test (Functions)
  • GitHub Check: E2E Service Test (GraphQL)
  • GitHub Check: E2E Service Test (Databases/Legacy)
  • GitHub Check: E2E Service Test (Databases/TablesDB)
  • GitHub Check: E2E Service Test (Console)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: E2E Service Test (Avatars)
  • GitHub Check: E2E Service Test (Account)
  • GitHub Check: E2E Service Test (Dev Keys)
  • GitHub Check: E2E Service Test (Site Screenshots)
  • GitHub Check: Unit Test
  • GitHub Check: E2E General Test
  • GitHub Check: scan
🔇 Additional comments (3)
app/init/resources.php (3)

28-28: LGTM - Imports correctly added.

The MaxMind Reader and Utopia Fetch Client imports are now present and support the new geoRecord resource.

Also applies to: 44-44


695-708: LGTM - Error handling enables graceful fallback.

The try-catch block appropriately handles failures from the geo service by logging errors and falling back to the local geodb. The exception thrown on line 701 is caught and logged rather than propagated, which allows the fallback mechanism to work correctly.


722-748: LGTM - Output construction handles all cases correctly.

The output array construction properly handles both successful geo lookups and fallback scenarios with appropriate defaults. Currency lookup logic correctly searches through configured currencies, and locale translations have fallback values.

Comment thread app/init/resources.php Outdated
Comment thread docker-compose.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/init/resources.php (1)

710-720: Add safety checks for array access in geodb fallback.

Lines 715-718 access nested array keys from $dbRecord without verifying they exist. If the geodb returns data in an unexpected format, this will cause undefined index errors. This issue was previously flagged and remains unaddressed.

Apply this diff to add safety checks:

     if (empty($record)) {
         // Fallback
         $dbRecord = $geodb->get($ip);
         if ($dbRecord) {
             $record = [];
-            $record['countryCode'] = $dbRecord['country']['iso_code'];
-            $record['country'] = $dbRecord['country']['names'];
-            $record['continent'] = $dbRecord['continent']['names'];
-            $record['continentCode'] = $dbRecord['continent']['code'];
+            $record['countryCode'] = $dbRecord['country']['iso_code'] ?? '--';
+            $record['country'] = $dbRecord['country']['names'] ?? [];
+            $record['continent'] = $dbRecord['continent']['names'] ?? [];
+            $record['continentCode'] = $dbRecord['continent']['code'] ?? '--';
         }
     }
🧹 Nitpick comments (1)
app/init/resources.php (1)

697-702: Simplify error handling—avoid throw-catch for expected fallback.

Throwing Exception::GENERAL_SERVER_ERROR on non-200 responses only to catch it immediately and set $record = null is unnecessarily complex. Since fallback to geodb is the intended behavior, simply check the status and skip the exception.

Apply this diff:

     $record = null;
     try {
         $record = $client->fetch("/ips/{$ip}", Client::METHOD_GET);
-        if ($record->getStatusCode() !== 200) {
-            $error = $record->json();
-            throw new Exception(Exception::GENERAL_SERVER_ERROR, $error['message'] ?? 'Failed to fetch geo data');
-        }
-        $record = $record->json();
+        $record = ($record->getStatusCode() === 200) ? $record->json() : null;
     } catch (Throwable $th) {
         Console::error($th->getMessage());
         Console::error($th->getTraceAsString());
         $record = null;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb4f1dc and 2137929.

📒 Files selected for processing (1)
  • app/init/resources.php (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/init/resources.php (2)
tests/e2e/Client.php (2)
  • Client (8-342)
  • addHeader (161-166)
src/Appwrite/Extend/Exception.php (1)
  • Exception (7-464)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: E2E Service Test (Users)
  • GitHub Check: E2E Service Test (Migrations)
  • GitHub Check: E2E Service Test (VCS)
  • GitHub Check: E2E Service Test (Tokens)
  • GitHub Check: E2E Service Test (Storage)
  • GitHub Check: E2E Service Test (Projects)
  • GitHub Check: E2E Service Test (Health)
  • GitHub Check: E2E Service Test (Sites)
  • GitHub Check: E2E Service Test (Databases/Legacy)
  • GitHub Check: E2E Service Test (Databases/TablesDB)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (Console)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: E2E Service Test (Avatars)
  • GitHub Check: E2E Service Test (Account)
  • GitHub Check: E2E Service Test (Site Screenshots)
  • GitHub Check: E2E Service Test (Dev Keys)
  • GitHub Check: Unit Test
  • GitHub Check: E2E General Test
  • GitHub Check: scan
🔇 Additional comments (1)
app/init/resources.php (1)

28-28: LGTM: Required imports added.

The MaxMind Reader and Utopia Fetch Client imports are correctly added to support the new geoRecord resource functionality. The Reader import also resolves the previous review comment about the missing type hint.

Also applies to: 44-44

Comment thread app/init/resources.php Outdated
Comment thread app/init/resources.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)

70-76: Guard geoRecord lookups and use Locale for missing country names

$geoRecord is indexed directly for both fields:

$output[$i]['countryCode'] = $geoRecord['countryCode'];
$output[$i]['countryName'] = $geoRecord['countryName'];

If the geo service omits either key, this will emit undefined index notices; $locale is also injected but unused.

Consider tightening this to be defensive and to leverage the locale fallback:

-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $countryCode = $geoRecord['countryCode'] ?? '--';
+            $countryName = $geoRecord['countryName']
+                ?? $locale->getText('locale.country.unknown');
+
+            $output[$i]['countryCode'] = $countryCode;
+            $output[$i]['countryName'] = $countryName;

This avoids notices when geo data is partial, gives a clearer UX for unknown locations, and makes the $locale parameter meaningful.

Also applies to: 79-80, 117-151

app/controllers/api/users.php (1)

2184-2193: Guard geoRecord when setting session countryCode

In the users createSession route, the session document is initialized with:

'ip' => $request->getIP(),
'countryCode' => $geoRecord['countryCode'],
'expire' => $expire,

If geoRecord is empty or lacks countryCode, this will trigger an undefined index notice. Downstream code already tolerates unknown country codes via the locale lookup.

Recommend guarding this with a default:

-                'ip' => $request->getIP(),
-                'countryCode' => $geoRecord['countryCode'],
+                'ip' => $request->getIP(),
+                'countryCode' => $geoRecord['countryCode'] ?? '--',

This keeps behavior intact when geo data is present and avoids runtime notices when it is not.

Also applies to: 2203-2219

♻️ Duplicate comments (7)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)

144-145: Defensively access countryCode / countryName from $geoRecord.

Directly indexing $geoRecord['countryCode'] / ['countryName'] will emit notices if the geo service returns incomplete data. Mirror the defensive style used for OS/client/device fields and fall back to a safe default.

-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? '';
+            $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';
app/config/collections/common.php (1)

870-1001: Previous review concerns still apply.

The comprehensive issues raised in the earlier review remain unaddressed in the current code:

  1. Latitude/longitude precision: size: 2 for float fields provides only ~1.1km accuracy; standard geolocation requires 6-8 decimal places for precision.
  2. Autonomous System Number data type: Stored as Database::VAR_STRING but represents a numeric value (0–4,294,967,295); should use Database::VAR_INTEGER.
  3. Missing indexes: None of the 12 new attributes have database indexes, which will cause slow queries if these fields are used for filtering or searching.
  4. Migration strategy: The deployment path for adding these attributes to existing session documents remains unclear.

Since you're in "Chill" mode, consider prioritizing items 1 and 2 for immediate correction, while items 3 and 4 can be evaluated based on actual query patterns and deployment procedures.

src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)

95-99: Harden geoRecord access when computing EU header

The new geoRecord injection and parameter wiring look fine, but this block is still brittle:

if (!empty($ip)) {
    if ($geoRecord) {
        $eu = Config::getParam('locale-eu');

        $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? '';
        $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? '';
        $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false';
    }
}

If countryCode is missing, the in_array call can trigger an undefined index notice. Prefer deriving locals with null‑coalescing and then using them:

-        $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? '';
-        $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? '';
-        $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false';
+        $countryCode = $geoRecord['countryCode'] ?? '';
+        $continentCode = $geoRecord['continentCode'] ?? '';
+
+        $headers['x-appwrite-country-code'] = $countryCode;
+        $headers['x-appwrite-continent-code'] = $continentCode;
+        $headers['x-appwrite-continent-eu'] = (!empty($countryCode) && \in_array($countryCode, $eu)) ? 'true' : 'false';

This avoids undefined index notices and keeps behavior unchanged when geo data is present.

Also applies to: 118-121, 236-243

app/controllers/api/users.php (1)

908-916: Make user log geo fields defensive and use locale fallback

The user‑logs route now injects geoRecord and maps it into each log entry:

$output[$i]['countryCode'] = $geoRecord['countryCode'];
$output[$i]['countryName'] = $geoRecord['countryName'];

If the geo service omits one of these keys you’ll get undefined index notices, and $locale is currently unused.

Align this with other endpoints by guarding the keys and using a sensible default:

-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $countryCode = $geoRecord['countryCode'] ?? '--';
+            $countryName = $geoRecord['countryName']
+                ?? $locale->getText('locale.country.unknown');
+
+            $output[$i]['countryCode'] = $countryCode;
+            $output[$i]['countryName'] = $countryName;

This eliminates undefined index notices and provides a clear “unknown country” label when geo data is missing.

Also applies to: 941-974

app/controllers/api/account.php (1)

194-195: Guard $geoRecord access for countryCode / countryName across sessions and logs

Several places assume $geoRecord['countryCode'] / $geoRecord['countryName'] always exist when creating sessions or enriching logs (token/magic/phone sessions via $createSession, email-password and anonymous sessions, OAuth2 sessions, and /v1/account/logs). Given geoRecord is populated from an external geo service and other code already treats its keys as optional, this can easily produce undefined-index notices when geo lookup fails or the payload shape changes.

Use a null‑coalescing fallback (or isset) for these fields so session creation and log rendering remain robust even when geo data is missing.

Example adjustments:

@@ function $createSession(...)
-            'countryCode' => $geoRecord['countryCode'],
+            'countryCode' => $geoRecord['countryCode'] ?? '',
@@ createEmailPasswordSession
-                'countryCode' => $geoRecord['countryCode'],
+                'countryCode' => $geoRecord['countryCode'] ?? '',
@@ createAnonymousSession
-                'countryCode' => $geoRecord['countryCode'],
+                'countryCode' => $geoRecord['countryCode'] ?? '',
@@ OAuth2 redirect session creation
-                'countryCode' => $geoRecord['countryCode'],
+                'countryCode' => $geoRecord['countryCode'] ?? '',
@@ /v1/account/logs action
-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? '';
+            $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';

Also applies to: 234-235, 927-933, 966-967, 1064-1068, 1129-1130, 1207-1211, 1401-1404, 1806-1807, 2912-2915, 2947-2948

app/controllers/api/teams.php (1)

1206-1208: Guard $geoRecord when setting countryCode on membership-confirmation session

In the membership status update flow, the auto-created session writes 'countryCode' => $geoRecord['countryCode'] without checking the key. If the geo service fails or returns a partial record, this will trigger undefined-index notices during a user-accept-invite flow.

Use a safe fallback here as well.

@@ ->inject('project')
-    ->inject('geoRecord')
+    ->inject('geoRecord')
@@ session payload
-                'factors' => ['email'],
-                'countryCode' => $geoRecord['countryCode'],
+                'factors' => ['email'],
+                'countryCode' => $geoRecord['countryCode'] ?? '',

Also applies to: 1278-1279

app/controllers/api/messaging.php (1)

1145-1146: geoRecord in log endpoints: add null-safety and confirm per-request vs per-log geo semantics

Across provider/topic/subscriber/message log endpoints you inject array $geoRecord and then index ['countryCode'] / ['countryName'] directly for every log row. Two points:

  1. Null-safety
    If the geo service fails or returns a partial record, those keys may be absent, leading to PHP notices. Other code (e.g. Functions/Http/Executions/Create) guards with ??. Suggest at least:
-            $output[$i]['countryCode'] = $geoRecord['countryCode'];
-            $output[$i]['countryName'] = $geoRecord['countryName'];
+            $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? null;
+            $output[$i]['countryName'] = $geoRecord['countryName'] ?? null;

and apply the same pattern in the topic, subscriber, and message log handlers.

  1. Semantics of geo on logs
    geoRecord appears to represent the current viewer’s IP, while each log entry has its own $log['ip']. With this change, all rows will show the same country for a given response, which is different from resolving each log’s IP (if that was the previous behavior). This is a potential audit/logging semantics regression; if logs are meant to reflect the geo of the event rather than the viewer, these endpoints probably need per-log geo (or persisted geo fields) instead of geoRecord.

Can you confirm what the intended contract is for countryCode/countryName on these log APIs and whether the per-request geo is acceptable here?

Also applies to: 1202-1203, 2546-2547, 2604-2605, 2961-2962, 3019-3020, 3757-3758, 3815-3816

🧹 Nitpick comments (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2)

70-76: Geo DI wiring looks consistent; consider documenting geoRecord shape.

->inject('geoRecord') aligns with the updated action() signature and matches the pattern used in Documents\Logs\XList, so wiring looks correct. To help future maintainers (and DI providers), consider adding a short docblock on action() describing the expected keys in $geoRecord (e.g., countryCode, countryName).


78-78: Signature matches injection order; unused $locale is benign.

The updated action() signature correctly mirrors the parameter/injection order (response, dbForProject, locale, geoRecord, authorization), so DI should remain stable. The $locale parameter is currently unused; given it’s part of the injection contract, it’s reasonable to leave it as-is rather than removing it and risking a mismatch. If PHPMD noise becomes an issue, you can suppress this specific rule at the method level instead of altering the signature.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 254219e and 1a569a5.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • app/config/collections/common.php (1 hunks)
  • app/controllers/api/account.php (13 hunks)
  • app/controllers/api/messaging.php (8 hunks)
  • app/controllers/api/teams.php (4 hunks)
  • app/controllers/api/users.php (4 hunks)
  • app/controllers/general.php (9 hunks)
  • app/controllers/shared/api/auth.php (1 hunks)
  • app/init/resources.php (3 hunks)
  • composer.json (1 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1 hunks)
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • composer.json
  • app/init/resources.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.

Applied to files:

  • app/controllers/api/users.php
  • app/controllers/api/messaging.php
🧬 Code graph analysis (7)
app/controllers/general.php (5)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (78-152)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
  • action (73-138)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)
  • action (68-133)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
app/controllers/api/users.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
app/controllers/api/messaging.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (78-152)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
app/controllers/api/account.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (78-152)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
app/controllers/api/teams.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (78-152)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
app/controllers/shared/api/auth.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php

79-79: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

app/controllers/general.php

59-59: Avoid unused parameters such as '$utopia'. (undefined)

(UnusedFormalParameter)

src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php

78-78: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan
🔇 Additional comments (3)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1)

53-53: Injection change verified as correct.

The parent class DocumentLogXList at line 79 has the updated action method signature with array $geoRecord parameter, confirming the injection change from geodb to geoRecord is properly aligned and valid.

src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1)

48-55: Geo record injection wiring looks consistent

->inject('geoRecord') is correctly added between locale and authorization, matching the expected action signature in the parent XList implementation.

app/controllers/shared/api/auth.php (1)

32-47: Geo denylist integration is wired correctly

Injecting geoRecord and reading $geoRecord['countryCode'] ?? '' for the console country denylist is consistent with the rest of the PR and avoids undefined index issues.

Comment thread app/controllers/api/teams.php Outdated
Comment thread app/controllers/general.php Outdated
lohanidamodar and others added 5 commits November 18, 2025 07:37
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ions/Logs/XList.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)

112-145: Critical: All log entries incorrectly share the same geo location.

The $geoRecord is request-scoped (injected once per request at line 78) and applied to all log entries in the loop, regardless of their individual IP addresses (line 128). Each log entry will show the same countryCode and countryName, even though the logs may originate from different IP addresses.

This is a data correctness issue: since each historical audit log stores its own ip address, the geo information should be resolved per-log from each log's IP, not from the current request's geo location.

The fix requires per-log geo resolution. Since no existing geo lookup pattern exists in the codebase for this purpose, you will need to either:

  1. Resolve geo information for each log's IP within the loop, or
  2. Implement batch geo lookup for all unique IPs in the result set before the loop
♻️ Duplicate comments (1)
app/controllers/api/account.php (1)

1401-1404: Fix TYPE::EMAIL typo and guard geoRecord in OAuth2 session creation

Two issues in the OAuth2 redirect session block:

  • TYPE::EMAIL (Line 1805) is almost certainly a typo for Type::EMAIL (matching the imported Appwrite\Auth\MFA\Type used elsewhere). As written, this will cause an undefined-constant error at runtime when this path is hit.
  • countryCode is read as $geoRecord['countryCode'] without a fallback, unlike other flows that use ?? ''. If the geo service omits this key, you’ll get undefined-index notices. This was previously flagged and fixed, so this looks like a regression.

Consider applying:

-                'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
-                'countryCode' => $geoRecord['countryCode'],
+                'factors' => [Type::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
+                'countryCode' => $geoRecord['countryCode'] ?? '',

Also applies to: 1793-1808

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a569a5 and 9ecb4e9.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • app/controllers/api/account.php (13 hunks)
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
app/controllers/api/account.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
  • action (78-152)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
  • action (79-157)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
  • action (101-507)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php

78-78: Avoid unused parameters such as '$locale'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan
🔇 Additional comments (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)

144-145: LGTM! Defensive checks properly implemented.

The null coalescing operators with empty string defaults correctly address the previous concern about undefined array key warnings when the geo service returns incomplete data.

app/controllers/api/account.php (2)

194-313: geoRecord wiring for token/magic/phone session flows looks consistent

The new array $geoRecord parameter on $createSession and the added ->inject('geoRecord') calls for the /sessions/token, /sessions/magic-url, and /sessions/phone routes are ordered correctly and match the closure signature. Using countryCode from $geoRecord['countryCode'] ?? '' inside the session document provides a safe default when geo lookup fails, without changing expiry or permissions behavior.

Also applies to: 1207-1211, 2548-2552, 2590-2594


927-933: Safe geoRecord usage in email-password and anonymous sessions

Injecting geoRecord into the email-password and anonymous session routes and populating countryCode via $geoRecord['countryCode'] ?? '' is aligned with the rest of the file and prevents undefined-index notices if the geo service returns an empty or partial payload. The type-hinted array $geoRecord parameters also keep the DI contract explicit.

Also applies to: 966-967, 1064-1068, 1129-1130

Comment thread app/controllers/api/account.php Outdated
Comment thread src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php Outdated
lohanidamodar and others added 7 commits June 9, 2026 04:02
The 1.9.x branch replaced the ollama service with appwrite-embedding,
but the merge left a stray 'ollama: condition: service_started' entry
in the appwrite service's depends_on. Compose rejected the project with
'service "appwrite" depends on undefined service "ollama"' and the
appwrite container failed to start in CI.
The three "geo" fields on a session record are actually properties of
the connecting network, not the geographic location:

  geoConnectionType    -> connectionType         (Cable/DSL, Cellular, ...)
  geoUserType          -> connectionUsageType    (hosting, residential, ...)
  geoOrganization      -> connectionOrganization

Aligns with how db-ip / MaxMind GeoIP2 categorise the fields ("traits"
of the network, not the location). connectionUsageType also matches
db-ip's REST naming for the same field (their REST returns "usageType";
the MMDB binary calls it "user_type" — confusing because the values
are usage profiles, not user roles).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ChiragAgg5k ChiragAgg5k changed the base branch from 1.9.x to main June 23, 2026 09:58
@lohanidamodar lohanidamodar requested a review from loks0n July 2, 2026 04:54
The Mails listener already filters on sessionAlerts, email presence,
magic-url/email-token provider, and first-session count — the wrapper
conditionals in the token-verify and OAuth session handlers were dead
code that survived a merge conflict resolution. Drop them and rely on
the listener as the single source of truth for alert eligibility.
- Usage.php: revert phpstan-consistent-constructor refactor (unrelated
  to docker-geo)
- general.php: revert docblock whitespace tweak
- Executions/Create.php: restore geo-empty guard to match general.php,
  use isEu() helper, drop stray double-parens
Match the placeholder pattern used by other secrets in .env
(_APP_OPENSSL_KEY_V1, _APP_EXECUTOR_SECRET) so deployments that
forget to override it fail loudly rather than sharing a
publicly-known bearer token.
…ields on GeoRecord

Restructure GeoRecord + getGeoForIp so the cached record holds raw geo data
and locale-translated country/continent names resolve lazily against the
current request locale. Extract the full session field set from the geo
service response (latitude/longitude/timeZone/weatherCode/postalCode/
autonomousSystem*/connection*/isp) via dedicated getters so account.php
callsites stop relying on ad-hoc getAttribute() lookups.

Normalize country/continent codes to uppercase before the EU check so the
lookup no longer depends on the geo API and locale-eu.php happening to
agree on case.

Add an isEmpty() override so geo-fencing guards in general.php and
Executions/Create.php actually key off "geo data is available" instead of
"the ArrayObject has any elements", which was always true.
…navailable

When _APP_CONSOLE_COUNTRIES_DENYLIST is configured and the geo lookup
returns an unknown country (service unreachable or IP not in database),
GeoRecord::getCountryCode() returns '--'. That value is never a member of
a real denylist, so the previous check silently let the request through
and bypassed the region policy.

Deny access when geo data is unavailable on the console project so the
denylist behaves as a hard region gate rather than a best-effort filter.
The fail-closed check on unknown country broke intra-network traffic:
local development, CI test runners, VPNs, and container-to-container
requests all present private or reserved IPs that MaxMind cannot resolve,
so every console request from those environments started returning 451.

Restrict the fail-closed path to public routable IPs. Private and reserved
ranges have no country to gate on, so the denylist is inapplicable and we
fall through to the normal auth flow. Real client traffic still hits the
fail-closed branch when geo data is missing.
…rvice

Distinguish "geo service returned a well-formed response with unknown
country" from "geo service could not be reached". The former is normal
for private and documentation IPs and must not block console traffic;
the latter is the security concern F5 called out, where an operator
cannot prove the caller sits outside the denied set.

Track lookup success on GeoRecord and only fail-closed when the service
did not respond successfully. This restores the auth flow for local
development and CI test IPs while still refusing access when geo is
misconfigured or offline against a configured denylist.
…memory cache

Swoole workers reuse the singleton getGeoForIp closure across all
requests. Capturing the Locale reference at first resolution would
lock every subsequent request to the first request's language, and the
per-worker $cache grew unbounded for the life of the process.

Pass Locale into the closure invocation instead of closing over it, and
rely on the per-request geoRecord resource to cache within a request.
MaxMind returned all region codes uppercase; the GeoRecord introduced
in this PR was returning countryCode lowercase while continentCode
stayed uppercase. That silently broke the public /v1/locale response,
the x-appwrite-country-code function header, and asymmetrically shaped
GeoRecord's API.

Return countryCode uppercase and normalize the console denylist
comparison to uppercase on both sides. Internal locale lookup keys
(countries.<code>) remain lowercase since that is the on-disk key.
…write

Convert the appwrite service's depends_on block to the long form so we
can require the geo container to pass its healthcheck (already defined)
before appwrite boots, instead of the default service_started which
races the geo listener and lets appwrite come up while lookups fail.
The long-form dict with condition: service_healthy on appwrite-geo
required the target to define a healthcheck (it doesn't) and broke
${_APP_DB_HOST:-mongodb} variable substitution in the dict key
position. Restore the short-form list dependency.
Previously getCountryCode() returned '--' when the raw code wasn't in
Appwrite's locale translations dictionary, while isEmpty() checked the
raw stored value — the mismatch let the console country denylist
silently bypass any country not translated in Appwrite (Antarctica,
recently-created ISO codes, etc.). Country code is a machine-readable
identifier; locale translation is only relevant for getCountryName().
@lohanidamodar

Copy link
Copy Markdown
Member Author

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.

5 participants