Fix merge conflicts

This commit is contained in:
Khushboo Verma 2025-07-07 12:36:42 +05:30
commit ea238bc5ec
40 changed files with 629 additions and 306 deletions

1
.env
View file

@ -22,6 +22,7 @@ _APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_CONSOLE_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost

View file

@ -283,8 +283,6 @@ jobs:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v4

View file

@ -188,16 +188,18 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
};
}, ['pools', 'cache']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('publisherRedis', function () {
// Stub
});
CLI::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);

View file

@ -5,7 +5,7 @@
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Validator\Origin;
use Appwrite\Network\Platform;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@ -23,7 +23,7 @@ $console = [
[
'$collection' => ID::custom('platforms'),
'name' => 'Localhost',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => 'localhost',
], // Current host is added on app init
],

View file

@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '8.1.0',
'version' => '8.1.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@ -250,7 +250,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '17.0.0',
'version' => '17.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,

View file

@ -6,13 +6,8 @@ use Utopia\System\System;
* List of Appwrite Sites templates
*/
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: Development override
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN', '');
$url = $protocol . '://' . $hostname;

View file

@ -21,6 +21,7 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Redirect;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@ -60,7 +61,6 @@ use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
@ -1182,8 +1182,8 @@ App::get('/v1/account/sessions/oauth2/:provider')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
@ -1282,7 +1282,6 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function (string $projectId, string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
@ -1779,8 +1778,8 @@ App::get('/v1/account/tokens/oauth2/:provider')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
@ -1860,7 +1859,7 @@ App::post('/v1/account/tokens/magic-url')
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
->inject('response')
@ -3146,7 +3145,7 @@ App::post('/v1/account/recovery')
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey'])
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey'])
->inject('request')
->inject('response')
->inject('user')
@ -3413,7 +3412,7 @@ App::post('/v1/account/verification')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{userId}')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey']) // TODO add built-in confirm page
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey']) // TODO add built-in confirm page
->inject('request')
->inject('response')
->inject('project')

View file

@ -523,11 +523,16 @@ App::get('/v1/health/queue/databases')
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('publisherRedis')
->inject('response')
->action(function (string $name, int|string $threshold, Publisher $publisher, Response $response) {
->action(function (string $name, int|string $threshold, Publisher $publisher, ?Publisher $publisherRedis, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue($name));
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'databases');
$size = $isRedisFallback
? $publisherRedis->getQueueSize(new Queue($name))
: $publisher->getQueueSize(new Queue($name));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@ -655,11 +660,16 @@ App::get('/v1/health/queue/migrations')
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('publisherRedis')
->inject('response')
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, ?Publisher $publisherRedis, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'migrations');
$size = $isRedisFallback
? $publisherRedis->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME))
: $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");

View file

@ -361,7 +361,7 @@ App::post('/v1/migrations/csv')
$hasCompression = $compression !== Compression::NONE;
$migrationId = ID::unique();
$newPath = $deviceForImports->getPath('/' . $migrationId . '_' . $fileId . '.csv');
$newPath = $deviceForImports->getPath($migrationId . '_' . $fileId . '.csv');
if ($hasEncryption || $hasCompression) {
$source = $deviceForFiles->read($path);
@ -410,8 +410,8 @@ App::post('/v1/migrations/csv')
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'statusCounters' => [],
'resourceData' => [],
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'path' => $newPath,

View file

@ -8,8 +8,8 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Validator\Event;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@ -1790,7 +1790,7 @@ App::post('/v1/projects/:projectId/platforms')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('type', null, new WhiteList([Platform::TYPE_WEB, Platform::TYPE_FLUTTER_WEB, Platform::TYPE_FLUTTER_IOS, Platform::TYPE_FLUTTER_ANDROID, Platform::TYPE_FLUTTER_LINUX, Platform::TYPE_FLUTTER_MACOS, Platform::TYPE_FLUTTER_WINDOWS, Platform::TYPE_APPLE_IOS, Platform::TYPE_APPLE_MACOS, Platform::TYPE_APPLE_WATCHOS, Platform::TYPE_APPLE_TVOS, Platform::TYPE_ANDROID, Platform::TYPE_UNITY, Platform::TYPE_REACT_NATIVE_IOS, Platform::TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)

View file

@ -11,6 +11,7 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Redirect;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@ -50,7 +51,6 @@ use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
@ -466,7 +466,7 @@ App::post('/v1/teams/:teamId/memberships')
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) // TODO add our own built-in confirm page
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')

View file

@ -649,6 +649,8 @@ App::get('/v1/users')
$total = $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$response->dynamic(new Document([
'users' => $users,

View file

@ -4,6 +4,7 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Redirect;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@ -53,7 +54,6 @@ use Utopia\Detector\Detector\Runtime;
use Utopia\Detector\Detector\Strategy;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
@ -116,8 +116,10 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$action = $isAuthorized ? ['type' => 'logs'] : ['type' => 'authorize', 'url' => $authorizeUrl];
@ -378,7 +380,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
}
@ -424,8 +426,8 @@ App::get('/v1/vcs/github/authorize')
type: MethodType::WEBAUTH,
hide: true,
))
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a successful installation attempt.', true, ['clients'])
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a failed installation attempt.', true, ['clients'])
->param('success', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a successful installation attempt.', true, ['platforms'])
->param('failure', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a failed installation attempt.', true, ['platforms'])
->inject('request')
->inject('response')
->inject('project')
@ -437,6 +439,8 @@ App::get('/v1/vcs/github/authorize')
]);
$appName = System::getEnv('_APP_VCS_GITHUB_APP_NAME');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
if (empty($appName)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.');
@ -444,7 +448,7 @@ App::get('/v1/vcs/github/authorize')
$url = "https://github.com/apps/$appName/installations/new?" . \http_build_query([
'state' => $state,
'redirect_uri' => $request->getProtocol() . '://' . $request->getHostname() . "/v1/vcs/github/callback"
'redirect_uri' => $protocol . '://' . $hostname . "/v1/vcs/github/callback"
]);
$response
@ -494,10 +498,12 @@ App::get('/v1/vcs/github/callback')
}
$region = $project->getAttribute('region', 'default');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$defaultState = [
'success' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
];
$state = \array_merge($defaultState, $state ?? []);

View file

@ -49,7 +49,6 @@ use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\System\System;
use Utopia\Validator\Hostname;
use Utopia\Validator\Text;
Config::setParam('domainVerification', false);
@ -76,7 +75,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$errorView = __DIR__ . '/../views/general/error.phtml';
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_DOMAIN', '');
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
if ($rule->isEmpty()) {
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
@ -267,7 +266,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
if (!$authorized) {
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_DOMAIN');
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
@ -453,7 +452,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$endpoint = $protocol . '://' . $hostname . "/v1";
@ -799,6 +798,7 @@ App::init()
->inject('getProjectDB')
->inject('locale')
->inject('localeCodes')
->inject('platforms')
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
@ -811,7 +811,7 @@ App::init()
->inject('apiKey')
->inject('httpReferrer')
->inject('httpReferrerSafe')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
/*
* Appwrite Router
*/
@ -1030,11 +1030,12 @@ App::init()
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$originValidator = new Origin($platforms);
if (
!$originValidator->isValid($origin)
&& $devKey->isEmpty()
$devKey->isEmpty()
&& !empty($origin)
&& !$originValidator->isValid($origin)
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('x-appwrite-key', ''))

View file

@ -19,6 +19,7 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Executor\Executor;
@ -79,9 +80,15 @@ App::setResource('localeCodes', function () {
App::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
App::setResource('publisherRedis', function () {
// Stub
});
App::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
App::setResource('consumerRedis', function () {
// Stub
});
App::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
@ -121,11 +128,11 @@ App::setResource('queueForCertificates', function (Publisher $publisher) {
App::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('clients', function ($request, $console, $project) {
App::setResource('platforms', function (Request $request, Document $console, Document $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
'name' => 'Current Host',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => $request->getHostname(),
], Document::SET_TYPE_APPEND);
@ -138,39 +145,32 @@ App::setResource('clients', function ($request, $console, $project) {
}
$console->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'name' => $hostname,
'hostname' => $hostname,
], Document::SET_TYPE_APPEND);
}
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
*/
$clientsConsole = \array_map(
fn ($node) => $node['hostname'],
\array_filter(
$console->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
)
);
$clients = $clientsConsole;
$platforms = $project->getAttribute('platforms', []);
foreach ($platforms as $node) {
if (
isset($node['type']) &&
($node['type'] === Origin::CLIENT_TYPE_WEB ||
$node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
!empty($node['hostname'])
) {
$clients[] = $node['hostname'];
}
// Add `exp` and `appwrite-callback-{projectId}` schemes
if (!$project->isEmpty() && $project->getId() !== 'console') {
$project->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_SCHEME,
'name' => 'Expo',
'key' => 'exp',
], Document::SET_TYPE_APPEND);
$project->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_SCHEME,
'name' => 'Appwrite Callback',
'key' => 'appwrite-callback-' . $project->getId(),
], Document::SET_TYPE_APPEND);
}
return \array_unique($clients);
return [
...$console->getAttribute('platforms', []),
...$project->getAttribute('platforms', []),
];
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
@ -222,7 +222,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
if (APP_MODE_ADMIN !== $mode) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
@ -232,8 +234,6 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
} else {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
}
if (
@ -264,7 +264,11 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
$user = $dbForProject->getDocument('users', $jwtUserId);
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', $jwtUserId);
} else {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
}
$jwtSessionId = $payload['sessionId'] ?? '';
@ -950,7 +954,7 @@ App::setResource('httpReferrer', function (Request $request): string {
return $referrer;
}, ['request']);
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $clients, Database $dbForPlatform, Document $project, App $utopia): string {
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $platforms, Database $dbForPlatform, Document $project, App $utopia): string {
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
@ -963,8 +967,8 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
}
// Safe if added as web platform
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$originValidator = new Origin($platforms);
if ($originValidator->isValid($request->getOrigin($httpReferrer))) {
return $referrer;
}
@ -992,4 +996,4 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
return $referrer;
}, ['request', 'httpReferrer', 'clients', 'dbForPlatform', 'project', 'utopia']);
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);

View file

@ -532,7 +532,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
}
$timelimit = $app->getResource('timelimit');
$console = $app->getResource('console'); /** @var Document $console */
$platforms = $app->getResource('platforms');
$user = $app->getResource('user'); /** @var Document $user */
/*
@ -557,9 +557,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
* Skip this check for non-web platforms which are not required to send an origin header.
*/
$origin = $request->getOrigin();
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$originValidator = new Origin($platforms);
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}

View file

@ -15,7 +15,7 @@ $labelClass = '';
$buttons = [];
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
// TODO: remove this later
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';

View file

@ -247,10 +247,18 @@ Server::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
Server::setResource('publisherRedis', function () {
// Stub
});
Server::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
Server::setResource('consumerRedis', function () {
// Stub
});
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);

57
composer.lock generated
View file

@ -3493,16 +3493,16 @@
},
{
"name": "utopia-php/database",
"version": "0.71.8",
"version": "0.71.9",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e"
"reference": "eb2f759020bba617e99dd67973a9bd949b47f54e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e",
"reference": "7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e",
"url": "https://api.github.com/repos/utopia-php/database/zipball/eb2f759020bba617e99dd67973a9bd949b47f54e",
"reference": "eb2f759020bba617e99dd67973a9bd949b47f54e",
"shasum": ""
},
"require": {
@ -3543,9 +3543,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.71.8"
"source": "https://github.com/utopia-php/database/tree/0.71.9"
},
"time": "2025-06-26T14:48:17+00:00"
"time": "2025-07-02T16:37:41+00:00"
},
{
"name": "utopia-php/detector",
@ -3993,16 +3993,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.10.1",
"version": "0.10.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f"
"reference": "0c85917482db172b3ccdc0704e42af3c1cc89361"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/ea1c585df7ec5f346f061a11581fc9a91679966f",
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/0c85917482db172b3ccdc0704e42af3c1cc89361",
"reference": "0c85917482db172b3ccdc0704e42af3c1cc89361",
"shasum": ""
},
"require": {
@ -4043,9 +4043,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.10.1"
"source": "https://github.com/utopia-php/migration/tree/0.10.4"
},
"time": "2025-05-26T15:29:19+00:00"
"time": "2025-07-02T18:31:09+00:00"
},
{
"name": "utopia-php/orchestration",
@ -4810,16 +4810,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.41.9",
"version": "0.41.11",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "61037c1ed9262308cab49c1d760f3278036ab694"
"reference": "60122cb613a5a1c82667ecc2217e351654a8d404"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/61037c1ed9262308cab49c1d760f3278036ab694",
"reference": "61037c1ed9262308cab49c1d760f3278036ab694",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/60122cb613a5a1c82667ecc2217e351654a8d404",
"reference": "60122cb613a5a1c82667ecc2217e351654a8d404",
"shasum": ""
},
"require": {
@ -4855,9 +4855,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.41.9"
"source": "https://github.com/appwrite/sdk-generator/tree/0.41.11"
},
"time": "2025-06-27T10:16:17+00:00"
"time": "2025-07-04T09:56:24+00:00"
},
{
"name": "doctrine/annotations",
@ -5084,16 +5084,16 @@
},
{
"name": "laravel/pint",
"version": "v1.22.1",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7"
"reference": "9ab851dba4faa51a3c3223dd3d07044129021024"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7",
"url": "https://api.github.com/repos/laravel/pint/zipball/9ab851dba4faa51a3c3223dd3d07044129021024",
"reference": "9ab851dba4faa51a3c3223dd3d07044129021024",
"shasum": ""
},
"require": {
@ -5104,10 +5104,10 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.75.0",
"illuminate/view": "^11.44.7",
"larastan/larastan": "^3.4.0",
"laravel-zero/framework": "^11.36.1",
"friendsofphp/php-cs-fixer": "^3.76.0",
"illuminate/view": "^11.45.1",
"larastan/larastan": "^3.5.0",
"laravel-zero/framework": "^11.45.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3.1",
"pestphp/pest": "^2.36.0"
@ -5117,6 +5117,9 @@
],
"type": "project",
"autoload": {
"files": [
"overrides/Runner/Parallel/ProcessFactory.php"
],
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
@ -5146,7 +5149,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-05-08T08:38:12+00:00"
"time": "2025-07-03T10:37:47+00:00"
},
{
"name": "matthiasmullie/minify",

View file

@ -116,6 +116,7 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
@ -213,7 +214,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:6.0.43
image: appwrite/console:6.1.2
restart: unless-stopped
networks:
- appwrite
@ -482,6 +483,7 @@ services:
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET

View file

@ -0,0 +1,16 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const databases = new sdk.Databases(client);
const result = await databases.upsertDocument(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
'<DOCUMENT_ID>', // documentId
{}, // data
["read("any")"] // permissions (optional)
);

View file

@ -10,5 +10,5 @@ const databases = new sdk.Databases(client);
const result = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents (optional)
[] // documents
);

View file

@ -1,5 +1,14 @@
# Change Log
## 8.1.1
* Fix circular dependency issue due to usage of `success` method in `utils.js` file from `parser.js` file
* Type generation fixes:
* Add ability to generate types directly to a specific file by passing a file path to `appwrite types output_path`, instead of just a directory
* Fix non-required attributes to not be null if default value is provided
* Fix `Models` import error
* Improve formatting and add auto-generated comments
## 8.1.0
* Add multi-region support to `init` command

View file

@ -1,13 +1,68 @@
# Change Log
## 17.1.0
* Add `upsertDocument` method
* Add `dart-3.8` and `flutter-3.32` runtimes
* Add `gif` image format
* Update bulk operation methods to reflect warning message
* Fix file parameter handling in chunked upload method
## 17.0.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
* Add `REGION` to doc examples due to the new multi region endpoints
* Add doc examples and methods for bulk api transactions: `createDocuments`, `deleteDocuments` etc.
* Add doc examples, class and methods for new `Sites` service
* Add doc examples, class and methods for new `Tokens` service
* Add enums for `BuildRuntime `, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Add enums for `BuildRuntime`, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Updates enum for `runtimes` with Pythonml312, Dart219, Flutter327 and Flutter329
* Add `token` param to `getFilePreview` and `getFileView` for File tokens usage
* Add `queries` and `search` params to `listMemberships` method
* Removes `search` param from `listExecutions` method
* Removes `search` param from `listExecutions` method
## 16.0.0
* Fix: remove content-type from GET requests
* Update (breaking): min and max params are now optional in `updateFloatAttribute` and `updateIntegerAttribute` methods (changes their positioning in method definition)
## 15.0.1
* Remove titles from all function descriptions
* Fix typing for collection "attribute" key
* Remove unnecessary awaits and asyncs
* Ensure `AppwriteException` response is always string
## 15.0.0
* Fix: pong response & chunked upload
## 14.2.0
* Add new push message parameters
## 14.1.0
* Support updating attribute name and size
## 14.0.0
* Support for Appwrite 1.6
* Add `key` attribute to `Runtime` response model.
* Add `buildSize` attribute to `Deployments` response model
* Add `scheduledAt` attribute to `Executions` response model
* Add `scopes` attribute to `Functions` response model
* Add `specifications` attribute to `Functions` response model
* Add new response model for `Specifications`
* Add new response model for `Builds`
* Add `createJWT()` : Enables creating a JWT using the `userId`
* Add `listSpecifications()`: Enables listing available runtime specifications
* Add `deleteExecution()` : Enables deleting executions
* Add `updateDeploymentBuild()`: Enables cancelling a deployment
* Add `scheduledAt` parameter to `createExecution()`: Enables creating a delayed execution
* Breaking changes
* Remove `otp` parameter from `deleteMFAAuthenticator`.
* Add `scopes` parameter for create/update function.
* Rename `templateBranch` to `templateVersion` in `createFunction()`.
* Rename `downloadDeployment()` to `getDeploymentDownload()`
> You can find the new syntax for breaking changes in the [Appwrite API references](https://appwrite.io/docs/references). Select version `1.6.x`.

View file

@ -226,7 +226,7 @@ class Mapper
];
if (!$rule['required']) {
$fields[$escapedKey]['defaultValue'] = $rule['default'];
$fields[$escapedKey]['defaultValue'] = $rule['default'] ?? null;
}
}

View file

@ -0,0 +1,159 @@
<?php
namespace Appwrite\Network;
class Platform
{
public const TYPE_UNKNOWN = 'unknown';
public const TYPE_WEB = 'web';
public const TYPE_FLUTTER_IOS = 'flutter-ios';
public const TYPE_FLUTTER_ANDROID = 'flutter-android';
public const TYPE_FLUTTER_MACOS = 'flutter-macos';
public const TYPE_FLUTTER_WINDOWS = 'flutter-windows';
public const TYPE_FLUTTER_LINUX = 'flutter-linux';
public const TYPE_FLUTTER_WEB = 'flutter-web';
public const TYPE_APPLE_IOS = 'apple-ios';
public const TYPE_APPLE_MACOS = 'apple-macos';
public const TYPE_APPLE_WATCHOS = 'apple-watchos';
public const TYPE_APPLE_TVOS = 'apple-tvos';
public const TYPE_ANDROID = 'android';
public const TYPE_UNITY = 'unity';
public const TYPE_REACT_NATIVE_IOS = 'react-native-ios';
public const TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
public const TYPE_SCHEME = 'scheme';
public const SCHEME_HTTP = 'http';
public const SCHEME_HTTPS = 'https';
public const SCHEME_CHROME_EXTENSION = 'chrome-extension';
public const SCHEME_FIREFOX_EXTENSION = 'moz-extension';
public const SCHEME_SAFARI_EXTENSION = 'safari-web-extension';
public const SCHEME_EDGE_EXTENSION = 'ms-browser-extension';
public const SCHEME_IOS = 'appwrite-ios';
public const SCHEME_MACOS = 'appwrite-macos';
public const SCHEME_WATCHOS = 'appwrite-watchos';
public const SCHEME_TVOS = 'appwrite-tvos';
public const SCHEME_ANDROID = 'appwrite-android';
public const SCHEME_WINDOWS = 'appwrite-windows';
public const SCHEME_LINUX = 'appwrite-linux';
/**
* @var array<string, string> Map scheme types to user-friendly platform names.
*/
private static array $names = [
self::SCHEME_HTTP => 'Web',
self::SCHEME_HTTPS => 'Web',
self::SCHEME_IOS => 'iOS',
self::SCHEME_MACOS => 'macOS',
self::SCHEME_WATCHOS => 'watchOS',
self::SCHEME_TVOS => 'tvOS',
self::SCHEME_ANDROID => 'Android',
self::SCHEME_WINDOWS => 'Windows',
self::SCHEME_LINUX => 'Linux',
self::SCHEME_CHROME_EXTENSION => 'Web (Chrome Extension)',
self::SCHEME_FIREFOX_EXTENSION => 'Web (Firefox Extension)',
self::SCHEME_SAFARI_EXTENSION => 'Web (Safari Extension)',
self::SCHEME_EDGE_EXTENSION => 'Web (Edge Extension)',
];
/**
* Get user-friendly platform name from a scheme.
*
* @param string|null $scheme
* @return string Empty string if scheme is not found.
*/
public static function getNameByScheme(?string $scheme): string
{
return self::$names[$scheme] ?? '';
}
public static function getHostnames(array $platforms): array
{
$hostnames = [];
foreach ($platforms as $platform) {
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
$hostname = strtolower($platform['hostname'] ?? '');
$key = strtolower($platform['key'] ?? '');
switch ($type) {
case self::TYPE_WEB:
case self::TYPE_FLUTTER_WEB:
if (!empty($hostname)) {
$hostnames[] = $hostname;
}
break;
case self::TYPE_FLUTTER_IOS:
case self::TYPE_FLUTTER_ANDROID:
case self::TYPE_FLUTTER_MACOS:
case self::TYPE_FLUTTER_WINDOWS:
case self::TYPE_FLUTTER_LINUX:
case self::TYPE_ANDROID:
case self::TYPE_APPLE_IOS:
case self::TYPE_APPLE_MACOS:
case self::TYPE_APPLE_WATCHOS:
case self::TYPE_APPLE_TVOS:
case self::TYPE_REACT_NATIVE_IOS:
case self::TYPE_REACT_NATIVE_ANDROID:
case self::TYPE_UNITY:
if (!empty($key)) {
$hostnames[] = $key;
}
break;
default:
break;
}
}
return array_unique($hostnames);
}
public static function getSchemes(array $platforms): array
{
$schemes = [];
foreach ($platforms as $platform) {
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
$scheme = strtolower($platform['key'] ?? '');
switch ($type) {
case self::TYPE_SCHEME:
if (!empty($scheme) && preg_match('/^[a-z][a-z0-9+.-]*$/', $scheme)) {
$schemes[] = $scheme;
}
break;
case self::TYPE_WEB:
case self::TYPE_FLUTTER_WEB:
$schemes[] = self::SCHEME_HTTP;
$schemes[] = self::SCHEME_HTTPS;
break;
case self::TYPE_FLUTTER_IOS:
case self::TYPE_APPLE_IOS:
case self::TYPE_REACT_NATIVE_IOS:
$schemes[] = self::SCHEME_IOS;
break;
case self::TYPE_FLUTTER_ANDROID:
case self::TYPE_ANDROID:
case self::TYPE_REACT_NATIVE_ANDROID:
$schemes[] = self::SCHEME_ANDROID;
break;
case self::TYPE_FLUTTER_MACOS:
case self::TYPE_APPLE_MACOS:
$schemes[] = self::SCHEME_MACOS;
break;
case self::TYPE_FLUTTER_WINDOWS:
case self::TYPE_UNITY:
$schemes[] = self::SCHEME_WINDOWS;
break;
case self::TYPE_FLUTTER_LINUX:
$schemes[] = self::SCHEME_LINUX;
break;
case self::TYPE_APPLE_WATCHOS:
$schemes[] = self::SCHEME_WATCHOS;
break;
case self::TYPE_APPLE_TVOS:
$schemes[] = self::SCHEME_TVOS;
break;
default:
break;
}
}
return array_unique($schemes);
}
}

View file

@ -2,150 +2,85 @@
namespace Appwrite\Network\Validator;
use Appwrite\Network\Platform;
use Utopia\Validator;
use Utopia\Validator\Hostname;
class Origin extends Validator
{
public const CLIENT_TYPE_UNKNOWN = 'unknown';
public const CLIENT_TYPE_WEB = 'web';
public const CLIENT_TYPE_FLUTTER_IOS = 'flutter-ios';
public const CLIENT_TYPE_FLUTTER_ANDROID = 'flutter-android';
public const CLIENT_TYPE_FLUTTER_MACOS = 'flutter-macos';
public const CLIENT_TYPE_FLUTTER_WINDOWS = 'flutter-windows';
public const CLIENT_TYPE_FLUTTER_LINUX = 'flutter-linux';
public const CLIENT_TYPE_FLUTTER_WEB = 'flutter-web';
public const CLIENT_TYPE_APPLE_IOS = 'apple-ios';
public const CLIENT_TYPE_APPLE_MACOS = 'apple-macos';
public const CLIENT_TYPE_APPLE_WATCHOS = 'apple-watchos';
public const CLIENT_TYPE_APPLE_TVOS = 'apple-tvos';
public const CLIENT_TYPE_ANDROID = 'android';
public const CLIENT_TYPE_UNITY = 'unity';
public const CLIENT_TYPE_REACT_NATIVE_IOS = 'react-native-ios';
public const CLIENT_TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
public const SCHEME_TYPE_HTTP = 'http';
public const SCHEME_TYPE_HTTPS = 'https';
public const SCHEME_TYPE_IOS = 'appwrite-ios';
public const SCHEME_TYPE_MACOS = 'appwrite-macos';
public const SCHEME_TYPE_WATCHOS = 'appwrite-watchos';
public const SCHEME_TYPE_TVOS = 'appwrite-tvos';
public const SCHEME_TYPE_ANDROID = 'appwrite-android';
public const SCHEME_TYPE_WINDOWS = 'appwrite-windows';
public const SCHEME_TYPE_LINUX = 'appwrite-linux';
protected array $hostnames = [];
protected array $schemes = [];
protected ?string $scheme = null;
protected ?string $host = null;
/**
* @var array
* Constructor
*
* @param array<\Utopia\Database\Document> $platforms
*/
protected $platforms = [
self::SCHEME_TYPE_HTTP => 'Web',
self::SCHEME_TYPE_HTTPS => 'Web',
self::SCHEME_TYPE_IOS => 'iOS',
self::SCHEME_TYPE_MACOS => 'macOS',
self::SCHEME_TYPE_WATCHOS => 'watchOS',
self::SCHEME_TYPE_TVOS => 'tvOS',
self::SCHEME_TYPE_ANDROID => 'Android',
self::SCHEME_TYPE_WINDOWS => 'Windows',
self::SCHEME_TYPE_LINUX => 'Linux',
];
/**
* @var array
*/
protected $clients = [
];
/**
* @var string
*/
protected $client = self::CLIENT_TYPE_UNKNOWN;
/**
* @var string
*/
protected $host = '';
/**
* @param string $target
*/
public function __construct($platforms)
public function __construct(array $platforms)
{
foreach ($platforms as $platform) {
$type = (isset($platform['type'])) ? $platform['type'] : '';
switch ($type) {
case self::CLIENT_TYPE_WEB:
case self::CLIENT_TYPE_FLUTTER_WEB:
$this->clients[] = (isset($platform['hostname'])) ? $platform['hostname'] : '';
break;
case self::CLIENT_TYPE_FLUTTER_IOS:
case self::CLIENT_TYPE_FLUTTER_ANDROID:
case self::CLIENT_TYPE_FLUTTER_MACOS:
case self::CLIENT_TYPE_FLUTTER_WINDOWS:
case self::CLIENT_TYPE_FLUTTER_LINUX:
case self::CLIENT_TYPE_ANDROID:
case self::CLIENT_TYPE_APPLE_IOS:
case self::CLIENT_TYPE_APPLE_MACOS:
case self::CLIENT_TYPE_APPLE_WATCHOS:
case self::CLIENT_TYPE_APPLE_TVOS:
case self::CLIENT_TYPE_REACT_NATIVE_IOS:
case self::CLIENT_TYPE_REACT_NATIVE_ANDROID:
$this->clients[] = (isset($platform['key'])) ? $platform['key'] : '';
break;
default:
# code...
break;
}
}
$this->hostnames = Platform::getHostnames($platforms);
$this->schemes = Platform::getSchemes($platforms);
}
public function getDescription(): string
{
if (!\array_key_exists($this->client, $this->platforms)) {
return 'Unsupported platform';
}
return 'Invalid Origin. Register your new client (' . $this->host . ') as a new '
. $this->platforms[$this->client] . ' platform on your project console dashboard';
}
/**
* Check if Origin has been allowed
* for access to the API
*
* @param mixed $origin
*
* Check if Origin is valid.
* @param mixed $origin The Origin URI.
* @return bool
*/
public function isValid($origin): bool
{
if (!is_string($origin)) {
$this->scheme = null;
$this->host = null;
if (!is_string($origin) || empty($origin)) {
return false;
}
$scheme = \parse_url($origin, PHP_URL_SCHEME);
$host = \parse_url($origin, PHP_URL_HOST);
$this->scheme = $this->parseScheme($origin);
$this->host = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
$this->host = $host;
$this->client = $scheme;
$webPlatforms = [
Platform::SCHEME_HTTP,
Platform::SCHEME_HTTPS,
Platform::SCHEME_CHROME_EXTENSION,
Platform::SCHEME_FIREFOX_EXTENSION,
Platform::SCHEME_SAFARI_EXTENSION,
Platform::SCHEME_EDGE_EXTENSION,
];
if (in_array($this->scheme, $webPlatforms, true)) {
$validator = new Hostname($this->hostnames);
return $validator->isValid($this->host);
}
if (empty($host)) {
if (!empty($this->scheme) && in_array($this->scheme, $this->schemes, true)) {
return true;
}
$validator = new Hostname($this->clients);
return false;
}
return $validator->isValid($host);
/**
* Get Description
* @return string
*/
public function getDescription(): string
{
$platform = $this->scheme ? Platform::getNameByScheme($this->scheme) : null;
$host = $this->host ? '(' . $this->host . ')' : '';
if (empty($this->host) && empty($this->scheme)) {
return 'Invalid Origin.';
}
return 'Invalid Origin. Register your new client ' . $host . ' as a new '
. $platform . ' platform on your project console dashboard';
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
@ -155,13 +90,35 @@ class Origin extends Validator
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
/**
* Parses the scheme from a URI string.
*
* @param string $uri The URI string to parse.
* @return string|null The extracted scheme string (e.g., "http", "exp", "mailto")
*/
public function parseScheme(string $uri): ?string
{
$uri = trim($uri);
if ($uri === '') {
return null; // No scheme in empty string
}
$scheme = parse_url($uri, PHP_URL_SCHEME);
if ($scheme === false) {
if (preg_match('/^([a-z][a-z0-9+.-]*):/i', $uri, $matches)) {
return $matches[1];
} else {
return null;
}
} else {
return $scheme;
}
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Appwrite\Network\Validator;
use Appwrite\Network\Platform;
class Redirect extends Origin
{
/**
* Get Description
* @return string
*/
public function getDescription(): string
{
$platform = Platform::getNameByScheme($this->scheme);
$host = $this->host ? '(' . $this->host . ')' : '';
if (empty($this->host) && empty($this->scheme)) {
return 'Invalid URI.';
}
return 'Invalid URI. Register your new client ' . $host . ' as a new '
. $platform . ' platform on your project console dashboard';
}
}

View file

@ -446,7 +446,7 @@ class Builds extends Action
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to push code repository: ' . $stderr);
@ -1473,7 +1473,7 @@ class Builds extends Action
$name = "{$resourceName} ({$projectName})";
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$projectId = $project->getId();
$region = $project->getAttribute('region', 'default');

View file

@ -26,6 +26,7 @@ abstract class ScheduleBase extends Action
protected array $schedules = [];
protected BrokerPool $publisher;
protected ?BrokerPool $publisherRedis = null;
private ?Histogram $collectSchedulesTelemetryDuration = null;
private ?Gauge $collectSchedulesTelemetryCount = null;
@ -72,6 +73,13 @@ abstract class ScheduleBase extends Action
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
$this->publisher = new BrokerPool($pools->get('publisher'));
try {
$this->publisherRedis = new BrokerPool($pools->get('publisherRedis'));
} catch (\Throwable) {
$this->publisherRedis = null;
}
$this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count');
$this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's');
$this->collectSchedulesTelemetryCount = $telemetry->createGauge('task.schedule.collect_schedules.count');

View file

@ -6,6 +6,7 @@ use Appwrite\Event\Func;
use Swoole\Coroutine as Co;
use Utopia\Database\Database;
use Utopia\Pools\Group;
use Utopia\System\System;
class ScheduleExecutions extends ScheduleBase
{
@ -29,9 +30,16 @@ class ScheduleExecutions extends ScheduleBase
protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
{
$queueForFunctions = new Func($this->publisher);
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
$queueForFunctions = new Func(
$isRedisFallback
? $this->publisherRedis
: $this->publisher
);
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
$dbForPlatform->deleteDocument(

View file

@ -8,6 +8,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Pools\Group;
use Utopia\System\System;
class ScheduleFunctions extends ScheduleBase
{
@ -90,7 +91,13 @@ class ScheduleFunctions extends ScheduleBase
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
$queueForFunctions = new Func($this->publisher);
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
$queueForFunctions = new Func(
$isRedisFallback
? $this->publisherRedis
: $this->publisher
);
$queueForFunctions
->setType('schedule')

View file

@ -314,6 +314,7 @@ class Migrations extends Action
$migration->getAttribute('resourceType')
);
}
$destination->shutDown();
$source->shutDown();

View file

@ -86,8 +86,8 @@ class Comment
$i = 0;
foreach ($projects as $projectId => $project) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN'));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$text .= "## {$project['name']}\n\n";
$text .= "Project ID: `{$projectId}`\n\n";
@ -200,8 +200,8 @@ class Comment
public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string
{
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN'));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$imageLight = $protocol . '://' . $hostname . $pathLight;
$imageDark = $protocol . '://' . $hostname . $pathDark;

View file

@ -1414,9 +1414,20 @@ trait DatabasesBase
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
$this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);
$this->assertEquals($releaseWithDate2['body']['key'], $movies['body']['indexes'][3]['key']);
foreach ($movies['body']['indexes'] as $index) {
$this->assertEquals('available', $index['status']);
}
$this->assertEventually(function () use ($databaseId, $data) {
$movies = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
foreach ($movies['body']['indexes'] as $index) {
$this->assertEquals('available', $index['status']);
}
return true;
}, 60000, 500);
return $data;
}

View file

@ -1470,9 +1470,24 @@ class DatabasesCustomServerTest extends Scope
$this->assertCount(64, $collection['body']['attributes']);
$this->assertCount(0, $collection['body']['indexes']);
foreach ($collection['body']['attributes'] as $attribute) {
$this->assertEquals('available', $attribute['status'], 'attribute: ' . $attribute['key']);
}
$this->assertEventually(function () use ($databaseId, $collectionId) {
$collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
foreach ($collection['body']['attributes'] ?? [] as $attribute) {
$this->assertEquals(
'available',
$attribute['status'],
'attribute: ' . $attribute['key']
);
}
return true;
}, 60000, 500);
// Test indexLimit = 64
// MariaDB, MySQL, and MongoDB create 6 indexes per new collection

View file

@ -4637,7 +4637,6 @@ class ProjectsConsoleClientTest extends Scope
'failure' => 'https://example.com'
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertStringContainsString('Invalid `success` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']);
/** Test oauth2 with devKey and now get oauth2 is disabled */
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [
@ -4660,7 +4659,6 @@ class ProjectsConsoleClientTest extends Scope
'url' => 'https://example.com',
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `url` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']);
/** Test hostname in Magic URL with devKey */
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [

View file

@ -16,13 +16,6 @@ trait RealtimeBase
$projectId = $this->getProject()['$id'];
}
$headers = array_merge(
[
"Origin" => "appwrite.test",
],
$headers
);
$query = [
"project" => $projectId,
"channels" => $channels,
@ -49,7 +42,7 @@ trait RealtimeBase
public function testConnectionFailureMissingChannels(): void
{
$client = $this->getWebsocket();
$client = $this->getWebsocket([]);
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey("type", $payload);
@ -64,14 +57,7 @@ trait RealtimeBase
public function testConnectionFailureUnknownProject(): void
{
$client = new WebSocketClient(
"ws://appwrite-traefik/v1/realtime?project=123",
[
"headers" => [
"Origin" => "appwrite.test",
],
]
);
$client = $this->getWebsocket(projectId: '123');
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey("type", $payload);

View file

@ -6,6 +6,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\System\System;
class SitesCustomClientTest extends Scope
{
@ -121,6 +122,7 @@ class SitesCustomClientTest extends Scope
* Test for SUCCESS
*/
$template = $this->getTemplate('starter-for-react');
$hostname = System::getEnv('_APP_DOMAIN') ?: '';
$this->assertEquals(200, $template['headers']['status-code']);
$this->assertIsArray($template['body']);
$this->assertEquals('starter-for-react', $template['body']['key']);
@ -129,8 +131,8 @@ class SitesCustomClientTest extends Scope
$this->assertEquals('github', $template['body']['vcsProvider']);
$this->assertEquals('Simple React application integrated with Appwrite SDK.', $template['body']['tagline']);
$this->assertIsArray($template['body']['frameworks']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']);
$this->assertEquals('http://'. $hostname . '/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']);
$this->assertEquals('http://' . $hostname . '/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']);
/**
* Test for FAILURE

View file

@ -2,6 +2,7 @@
namespace Tests\Unit\Network\Validators;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Helpers\ID;
@ -14,62 +15,96 @@ class OriginTest extends TestCase
[
'$collection' => ID::custom('platforms'),
'name' => 'Production',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => 'appwrite.io',
],
[
'$collection' => ID::custom('platforms'),
'name' => 'Development',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => 'appwrite.test',
],
[
'$collection' => ID::custom('platforms'),
'name' => 'Localhost',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => 'localhost',
],
[
'$collection' => ID::custom('platforms'),
'name' => 'Flutter',
'type' => Origin::CLIENT_TYPE_FLUTTER_WEB,
'type' => Platform::TYPE_FLUTTER_WEB,
'hostname' => 'appwrite.flutter',
],
[
'$collection' => ID::custom('platforms'),
'name' => 'Expo',
'type' => Platform::TYPE_SCHEME,
'key' => 'exp',
],
[
'$collection' => ID::custom('platforms'),
'name' => 'Appwrite Callback',
'type' => Platform::TYPE_SCHEME,
'key' => 'appwrite-callback-123',
],
]);
$this->assertEquals($validator->isValid('https://localhost'), true);
$this->assertEquals($validator->isValid('http://localhost'), true);
$this->assertEquals($validator->isValid('http://localhost:80'), true);
$this->assertEquals(false, $validator->isValid(''));
$this->assertEquals(false, $validator->isValid('/'));
$this->assertEquals($validator->isValid('https://appwrite.io'), true);
$this->assertEquals($validator->isValid('http://appwrite.io'), true);
$this->assertEquals($validator->isValid('http://appwrite.io:80'), true);
$this->assertEquals(true, $validator->isValid('https://localhost'));
$this->assertEquals(true, $validator->isValid('http://localhost'));
$this->assertEquals(true, $validator->isValid('http://localhost:80'));
$this->assertEquals($validator->isValid('https://appwrite.test'), true);
$this->assertEquals($validator->isValid('http://appwrite.test'), true);
$this->assertEquals($validator->isValid('http://appwrite.test:80'), true);
$this->assertEquals(true, $validator->isValid('https://appwrite.io'));
$this->assertEquals(true, $validator->isValid('http://appwrite.io'));
$this->assertEquals(true, $validator->isValid('http://appwrite.io:80'));
$this->assertEquals($validator->isValid('https://appwrite.flutter'), true);
$this->assertEquals($validator->isValid('http://appwrite.flutter'), true);
$this->assertEquals($validator->isValid('http://appwrite.flutter:80'), true);
$this->assertEquals(true, $validator->isValid('https://appwrite.test'));
$this->assertEquals(true, $validator->isValid('http://appwrite.test'));
$this->assertEquals(true, $validator->isValid('http://appwrite.test:80'));
$this->assertEquals($validator->isValid('https://example.com'), false);
$this->assertEquals($validator->isValid('http://example.com'), false);
$this->assertEquals($validator->isValid('http://example.com:80'), false);
$this->assertEquals(true, $validator->isValid('https://appwrite.flutter'));
$this->assertEquals(true, $validator->isValid('http://appwrite.flutter'));
$this->assertEquals(true, $validator->isValid('http://appwrite.flutter:80'));
$this->assertEquals($validator->isValid('appwrite-ios://com.company.appname'), false);
$this->assertEquals($validator->getDescription(), 'Invalid Origin. Register your new client (com.company.appname) as a new iOS platform on your project console dashboard');
$this->assertEquals(false, $validator->isValid('https://example.com'));
$this->assertEquals(false, $validator->isValid('http://example.com'));
$this->assertEquals(false, $validator->isValid('http://example.com:80'));
$this->assertEquals($validator->isValid('appwrite-android://com.company.appname'), false);
$this->assertEquals($validator->getDescription(), 'Invalid Origin. Register your new client (com.company.appname) as a new Android platform on your project console dashboard');
$this->assertEquals(true, $validator->isValid('exp://'));
$this->assertEquals(true, $validator->isValid('exp:///'));
$this->assertEquals(true, $validator->isValid('exp://index'));
$this->assertEquals($validator->isValid('appwrite-macos://com.company.appname'), false);
$this->assertEquals($validator->getDescription(), 'Invalid Origin. Register your new client (com.company.appname) as a new macOS platform on your project console dashboard');
$this->assertEquals(true, $validator->isValid('appwrite-callback-123://'));
$this->assertEquals(false, $validator->isValid('appwrite-callback-456://'));
$this->assertEquals($validator->isValid('appwrite-linux://com.company.appname'), false);
$this->assertEquals($validator->getDescription(), 'Invalid Origin. Register your new client (com.company.appname) as a new Linux platform on your project console dashboard');
$this->assertEquals(false, $validator->isValid('appwrite-ios://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new iOS platform on your project console dashboard', $validator->getDescription());
$this->assertEquals($validator->isValid('appwrite-windows://com.company.appname'), false);
$this->assertEquals($validator->getDescription(), 'Invalid Origin. Register your new client (com.company.appname) as a new Windows platform on your project console dashboard');
$this->assertEquals(false, $validator->isValid('appwrite-android://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Android platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('appwrite-macos://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new macOS platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('appwrite-linux://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Linux platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('appwrite-windows://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Windows platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('chrome-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Chrome Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('moz-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Firefox Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('safari-web-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Safari Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('ms-browser-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Edge Extension) platform on your project console dashboard', $validator->getDescription());
}
}