Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migration-cleanup

# Conflicts:
#	src/Appwrite/Platform/Workers/Migrations.php
This commit is contained in:
fogelito 2025-12-16 09:31:58 +02:00
commit 6fbacb484b
20 changed files with 139 additions and 58 deletions

1
.env
View file

@ -125,3 +125,4 @@ _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for

View file

@ -357,6 +357,15 @@ return [
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_TRUSTED_HEADERS',
'description' => 'This option allows you to set the list of trusted headers, the value is a commaseparated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.',
'introduction' => '1.8.0',
'default' => 'x-forwarded-for',
'required' => false,
'question' => '',
'filter' => ''
]
],
],

View file

@ -628,7 +628,6 @@ App::init()
$queueForFunctions->setPlatform($platform);
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
$queueForMigrations->setPlatform($platform);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.

30
composer.lock generated
View file

@ -4264,16 +4264,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.34",
"version": "0.33.35",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "76def92594c32504ec80eaacdb60ff8fad73c856"
"reference": "82b139fb04f30045db51b0d322224f222da32313"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/76def92594c32504ec80eaacdb60ff8fad73c856",
"reference": "76def92594c32504ec80eaacdb60ff8fad73c856",
"url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313",
"reference": "82b139fb04f30045db51b0d322224f222da32313",
"shasum": ""
},
"require": {
@ -4306,9 +4306,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.34"
"source": "https://github.com/utopia-php/http/tree/0.33.35"
},
"time": "2025-12-08T07:55:31+00:00"
"time": "2025-12-12T08:33:52+00:00"
},
{
"name": "utopia-php/image",
@ -5011,22 +5011,22 @@
},
{
"name": "utopia-php/swoole",
"version": "0.8.4",
"version": "0.8.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
"reference": "150c30700e738c52348cce9ed0e0f0ff96872081"
"reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/150c30700e738c52348cce9ed0e0f0ff96872081",
"reference": "150c30700e738c52348cce9ed0e0f0ff96872081",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/e42b6b8e44c457a7b35d8a857d7af1d67d667c58",
"reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58",
"shasum": ""
},
"require": {
"ext-swoole": "*",
"php": ">=8.0",
"utopia-php/framework": "0.33.*"
"utopia-php/framework": "0.33.35"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -5056,9 +5056,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
"source": "https://github.com/utopia-php/swoole/tree/0.8.4"
"source": "https://github.com/utopia-php/swoole/tree/0.8.5"
},
"time": "2025-09-07T09:39:46+00:00"
"time": "2025-12-15T14:03:23+00:00"
},
{
"name": "utopia-php/system",
@ -8943,7 +8943,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -8967,5 +8967,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}

View file

@ -220,6 +220,7 @@ services:
- _APP_DATABASE_SHARED_NAMESPACE
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
extra_hosts:
- "host.docker.internal:host-gateway"
@ -736,6 +737,7 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_DATABASE_SHARED_TABLES
- _APP_OPTIONS_FORCE_HTTPS
appwrite-task-maintenance:
entrypoint: maintenance

View file

@ -2,6 +2,7 @@
namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
@ -110,13 +111,18 @@ class Build extends Event
*/
protected function preparePayload(): array
{
$platform = $this->platform;
if (empty($platform)) {
$platform = Config::getParam('platform', []);
}
return [
'project' => $this->project,
'resource' => $this->resource,
'deployment' => $this->deployment,
'type' => $this->type,
'template' => $this->template,
'platform' => $this->platform
'platform' => $platform,
];
}

View file

@ -161,7 +161,7 @@ class Database extends Event
return $this->document;
}
public function setProject(Document $project): self
public function setProject(Document $project): static
{
$database = $project->getAttribute('database');
if (!empty($database)) {

View file

@ -92,9 +92,9 @@ class Event
* Set queue used for this event.
*
* @param string $queue
* @return Event
* @return static
*/
public function setQueue(string $queue): self
public function setQueue(string $queue): static
{
$this->queue = $queue;
@ -114,9 +114,9 @@ class Event
/**
* Set event name used for this event.
* @param string $event
* @return Event
* @return static
*/
public function setEvent(string $event): self
public function setEvent(string $event): static
{
$this->event = $event;
@ -137,9 +137,9 @@ class Event
* Set project for this event.
*
* @param Document $project
* @return self
* @return static
*/
public function setProject(Document $project): self
public function setProject(Document $project): static
{
$this->project = $project;
return $this;
@ -159,9 +159,9 @@ class Event
* Set platform for this event.
*
* @param array $platform
* @return self
* @return static
*/
public function setPlatform(array $platform): self
public function setPlatform(array $platform): static
{
$this->platform = $platform;
return $this;
@ -181,9 +181,9 @@ class Event
* Set user for this event.
*
* @param Document $user
* @return self
* @return static
*/
public function setUser(Document $user): self
public function setUser(Document $user): static
{
$this->user = $user;
@ -193,9 +193,9 @@ class Event
/**
* Set user ID for this event.
*
* @return self
* @return static
*/
public function setUserId(string $userId): self
public function setUserId(string $userId): static
{
$this->userId = $userId;
@ -225,9 +225,9 @@ class Event
*
* @param array $payload
* @param array $sensitive
* @return self
* @return static
*/
public function setPayload(array $payload, array $sensitive = []): self
public function setPayload(array $payload, array $sensitive = []): static
{
$this->payload = $payload;

View file

@ -2,6 +2,7 @@
namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
@ -202,6 +203,11 @@ class Func extends Event
{
$events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null;
$platform = $this->platform;
if (empty($platform)) {
$platform = Config::getParam('platform', []);
}
return [
'project' => $this->project,
'user' => $this->user,
@ -217,7 +223,7 @@ class Func extends Event
'path' => $this->path,
'headers' => $this->headers,
'method' => $this->method,
'platform' => $this->platform
'platform' => $platform,
];
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Queue\Publisher;
class Mail extends Event
@ -516,6 +517,11 @@ class Mail extends Event
*/
protected function preparePayload(): array
{
$platform = $this->platform;
if (empty($platform)) {
$platform = Config::getParam('platform', []);
}
return [
'project' => $this->project,
'recipient' => $this->recipient,
@ -528,7 +534,8 @@ class Mail extends Event
'variables' => $this->variables,
'attachment' => $this->attachment,
'customMailOptions' => $this->customMailOptions,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
'events' => Event::generateEvents($this->getEvent(), $this->getParams()),
'platform' => $platform,
];
}
}

View file

@ -161,19 +161,6 @@ class Messaging extends Event
return $this->scheduledAt;
}
/**
* Set project for this event.
*
* @param Document $project
* @return self
*/
public function setProject(Document $project): self
{
$this->project = $project;
return $this;
}
/**
* Prepare the payload for the event
*

View file

@ -2,6 +2,7 @@
namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
@ -73,11 +74,16 @@ class Migration extends Event
*/
protected function preparePayload(): array
{
$platform = $this->platform;
if (empty($platform)) {
$platform = Config::getParam('platform', []);
}
return [
'project' => $this->project,
'user' => $this->user,
'migration' => $this->migration,
'platform' => $this->platform,
'platform' => $platform,
];
}
}

View file

@ -177,6 +177,7 @@ class Decrement extends Action
value: $value,
min: $min
);
$document->setAttribute('$' . $this->getCollectionsEventsContext() . 'Id', $collectionId);
} catch (ConflictException) {
throw new Exception($this->getConflictException());
} catch (NotFoundException) {

View file

@ -177,6 +177,7 @@ class Increment extends Action
value: $value,
max: $max
);
$document->setAttribute('$' . $this->getCollectionsEventsContext() . 'Id', $collectionId);
} catch (ConflictException) {
throw new Exception($this->getConflictException());
} catch (NotFoundException) {

View file

@ -62,9 +62,6 @@ class Messaging extends Action
*/
public function __construct()
{
$this->adapter = $this->createInternalSMSAdapter();
$this
->desc('Messaging worker')
->inject('message')
@ -390,6 +387,10 @@ class Messaging extends Action
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Log $log): void
{
if ($this->adapter === null) {
$this->adapter = $this->createInternalSMSAdapter();
}
if ($this->adapter === null) {
Console::warning('Skipped SMS processing. SMS adapter is not set.');
return;

View file

@ -47,7 +47,6 @@ class Migrations extends Action
protected ?Device $deviceForFiles;
protected ?Document $project;
protected array $plan = [];
protected array $platform = [];
/**
* @var array<string, int>
@ -103,7 +102,6 @@ class Migrations extends Action
$this->deviceForMigrations = $deviceForMigrations;
$this->deviceForFiles = $deviceForFiles;
$this->plan = $plan;
$this->platform = $payload['platform'] ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
@ -135,7 +133,6 @@ class Migrations extends Action
$this->deviceForMigrations = null;
$this->deviceForFiles = null;
$this->plan = [];
$this->platform = [];
$this->sourceReport = [];
gc_collect_cycles();
@ -153,10 +150,15 @@ class Migrations extends Action
$credentials = $migration->getAttribute('credentials');
$migrationOptions = $migration->getAttribute('options');
$dataSource = Appwrite::SOURCE_API;
$endpoint = $this->platform['endpoint'] ?: ($credentials['endpoint'] ?? 'http://appwrite.test/v1');
$database = null;
$queries = [];
if ($credentials['endpoint'] === 'http://localhost/v1') {
$platform = Config::getParam('platform', []);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$credentials['endpoint'] = $protocol . '://' . $platform['apiHostname'] . '/v1';
}
if ($source === Appwrite::getName() && $destination === DestinationCSV::getName()) {
$dataSource = Appwrite::SOURCE_DATABASE;
$database = $this->dbForProject;
@ -187,7 +189,7 @@ class Migrations extends Action
),
SourceAppwrite::getName() => new SourceAppwrite(
$credentials['projectId'],
$endpoint,
$credentials['endpoint'],
$credentials['apiKey'],
$dataSource,
$database,
@ -215,10 +217,13 @@ class Migrations extends Action
$destination = $migration->getAttribute('destination');
$options = $migration->getAttribute('options', []);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$platform = Config::getParam('platform', []);
return match ($destination) {
DestinationAppwrite::getName() => new DestinationAppwrite(
$this->project->getId(),
$this->platform['endpoint'],
$protocol . '://' . $platform['apiHostname'] . '/v1',
$apiKey,
$this->dbForProject,
Config::getParam('collections', [])['databases']['collections'],
@ -322,8 +327,13 @@ class Migrations extends Action
) {
$credentials = $migration->getAttribute('credentials', []);
$credentials['projectId'] = $credentials['projectId'] ?? $project->getId();
$credentials['endpoint'] = $credentials['endpoint'] ?? $this->platform['endpoint'];
$credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey;
if (empty($credentials['endpoint'])) {
$platform = Config::getParam('platform', []);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$credentials['endpoint'] = $protocol . '://' . $platform['apiHostname'] . '/v1';
}
$migration->setAttribute('credentials', $credentials);
}

View file

@ -9,6 +9,7 @@ use Swoole\Http\Request as SwooleRequest;
use Utopia\Database\Validator\Authorization;
use Utopia\Route;
use Utopia\Swoole\Request as UtopiaRequest;
use Utopia\System\System;
class Request extends UtopiaRequest
{
@ -20,6 +21,9 @@ class Request extends UtopiaRequest
public function __construct(SwooleRequest $request)
{
$trustedHeaders = System::getEnv('_APP_TRUSTED_HEADERS', 'x-forwarded-for');
$this->setTrustedIpHeaders(explode(',', $trustedHeaders));
parent::__construct($request);
}

View file

@ -326,4 +326,41 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 204);
}
public function testFallbackForTrustedIp(): void
{
$email = uniqid() . 'user@localhost.test';
$password = 'password';
$name = 'User Name';
// call appwrite directly to avoid proxy stripping the headers
$this->client->setEndpoint('http://localhost/v1');
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-forwarded-for' => '191.0.113.195',
]), [
'userId' => ID::unique(),
'email' => $email,
'password' => $password,
'name' => $name,
]);
$this->assertEquals($response['headers']['status-code'], 201);
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-forwarded-for' => '191.0.113.195',
]), [
'email' => $email,
'password' => $password,
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals('191.0.113.195', $response['body']['clientIp'] ?? $response['body']['ip'] ?? '');
}
}

View file

@ -6118,6 +6118,7 @@ trait DatabasesBase
]));
$this->assertEquals(200, $inc['headers']['status-code']);
$this->assertEquals(6, $inc['body']['count']);
$this->assertEquals($collectionId, $inc['body']['$collectionId']);
// Verify count = 6
$get = $this->client->call(Client::METHOD_GET, "/databases/$databaseId/collections/$collectionId/documents/$docId", array_merge([
@ -6229,6 +6230,7 @@ trait DatabasesBase
]));
$this->assertEquals(200, $dec['headers']['status-code']);
$this->assertEquals(9, $dec['body']['count']);
$this->assertEquals($collectionId, $dec['body']['$collectionId']);
$get = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',

View file

@ -7760,6 +7760,7 @@ trait DatabasesBase
'x-appwrite-project' => $this->getProject()['$id'],
]));
$this->assertEquals(200, $inc['headers']['status-code']);
$this->assertEquals($tableId, $inc['body']['$tableId']);
$this->assertEquals(6, $inc['body']['count']);
// Verify count = 6
@ -7872,6 +7873,7 @@ trait DatabasesBase
]));
$this->assertEquals(200, $dec['headers']['status-code']);
$this->assertEquals(9, $dec['body']['count']);
$this->assertEquals($tableId, $dec['body']['$tableId']);
$get = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([
'content-type' => 'application/json',