Compare commits

..
Author SHA1 Message Date
Christy Jacob 33fae9cce8 chore: update composer.json and composer.lock for utopia-php/queue and add appwrite-labs/php-amqplib
- Updated utopia-php/queue version to dev-feat-swoole-amqp-adapter and aliased it to 0.11.1 in composer.json.
- Added appwrite-labs/php-amqplib package with version 0.1.0 to composer.lock.
- Updated phpseclib/phpseclib to version 3.0.45 in composer.lock.
- Removed php-amqplib/php-amqplib from composer.lock.
2025-06-23 23:53:35 +04:00
Christy Jacob 917677a002 Merge branch '1.7.x' of github.com:appwrite/appwrite into 1.7.x 2025-06-23 23:51:40 +04:00
Christy Jacob 25d7b37ee0 Merge branches 'chore-update-env-var-used-for-domain' and '1.7.x' of github.com:appwrite/appwrite into 1.7.x 2025-06-20 17:33:52 +04:00
207 changed files with 1439 additions and 2296 deletions
+2 -3
View File
@@ -85,9 +85,8 @@ _APP_COMPUTE_MAINTENANCE_INTERVAL=600
_APP_COMPUTE_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://exc1/v1
_APP_BROWSER_HOST=http://appwrite-browser:3000/v1
_APP_FUNCTIONS_RUNTIMES=node-22
_APP_SITES_RUNTIMES=static-1,node-22
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.32
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_START_TIME=12:00
_APP_MAINTENANCE_RETENTION_CACHE=2592000
+2 -3
View File
@@ -64,9 +64,8 @@ jobs:
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
sudo apt update
sudo apt install oha
oha --version
- name: Benchmark PR
run: 'oha -z 180s http://localhost/v1/health/version --output-format json > benchmark.json'
run: 'oha -z 180s http://localhost/v1/health/version -j > benchmark.json'
- name: Cleaning
run: docker compose down -v
- name: Installing latest version
@@ -79,7 +78,7 @@ jobs:
docker compose up -d
sleep 10
- name: Benchmark Latest
run: oha -z 180s http://localhost/v1/health/version --output-format json > benchmark-latest.json
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
- name: Prepare comment
run: |
echo '## :sparkles: Benchmark results' > benchmark.txt
+4 -85
View File
@@ -178,7 +178,6 @@ jobs:
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's|^_APP_BROWSER_HOST=.*|_APP_BROWSER_HOST=http://invalid-browser/v1|' .env
docker compose up -d
sleep 30
@@ -199,7 +198,7 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
@@ -278,12 +277,14 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v4
@@ -359,85 +360,3 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
e2e_screenshots_keys:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 30
- name: Run Site tests with browser connected in dedicated table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
e2e_screenshots_shared_mode:
name: E2E Shared Mode Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
strategy:
fail-fast: false
matrix:
tables-mode: [
'Shared V1',
'Shared V2',
]
steps:
- name: checkout
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 30
- name: Run Site tests with browser connected in ${{ matrix.tables-mode }} table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
+4 -6
View File
@@ -188,18 +188,16 @@ 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']);
+2 -2
View File
@@ -5,7 +5,7 @@
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@@ -23,7 +23,7 @@ $console = [
[
'$collection' => ID::custom('platforms'),
'name' => 'Localhost',
'type' => Platform::TYPE_WEB,
'type' => Origin::CLIENT_TYPE_WEB,
'hostname' => 'localhost',
], // Current host is added on app init
],
+6 -7
View File
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '17.0.2',
'version' => '17.0.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.10.1',
'version' => '0.10.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '8.2.2',
'version' => '8.0.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -231,7 +231,6 @@ return [
'gitRepoName' => 'sdk-for-cli',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'master',
'exclude' => [
'services' => [
['name' => 'assistant'],
@@ -251,7 +250,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '17.1.0',
'version' => '17.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -359,7 +358,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.14.0',
'version' => '0.13.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -377,7 +376,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '16.1.0',
'version' => '16.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
+1 -1
View File
@@ -7,7 +7,7 @@ use Utopia\System\System;
*/
$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', ''));
$url = $protocol . '://' . $hostname;
+38 -94
View File
@@ -21,7 +21,6 @@ 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;
@@ -61,6 +60,7 @@ 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,23 +1182,16 @@ 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 ($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('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('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')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$protocol = $request->getProtocol();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1223,20 +1216,12 @@ App::get('/v1/account/sessions/oauth2/:provider')
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$redirectBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$redirectBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$redirectBase .= ':' . $port;
}
if (empty($success)) {
$success = $redirectBase . $oauthDefaultSuccess;
$success = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $redirectBase . $oauthDefaultFailure;
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1266,14 +1251,9 @@ App::get('/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) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1282,7 +1262,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1302,14 +1282,9 @@ 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) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1318,7 +1293,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1342,24 +1317,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('request')
->inject('response')
->inject('project')
->inject('platforms')
->inject('devKey')
->inject('user')
->inject('dbForProject')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$redirect = new Redirect($platforms);
$validateURL = new URL();
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
@@ -1386,11 +1352,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state = $defaultState;
}
if ($devKey->isEmpty() && !$redirect->isValid($state['success'])) {
if (!$validateURL->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirect->isValid($state['failure'])) {
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$failure = [];
@@ -1457,13 +1423,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$name = '';
$nameOAuth = $oauth2->getUserName($accessToken);
$userParam = $request->getParam('user');
$userParam = \json_decode($request->getParam('user'), true);
if (!empty($nameOAuth)) {
$name = $nameOAuth;
} elseif ($userParam !== null) {
$userDecoded = \json_decode($userParam, true);
if (isset($userDecoded['name']['firstName']) && isset($userDecoded['name']['lastName'])) {
$name = $userDecoded['name']['firstName'] . ' ' . $userDecoded['name']['lastName'];
} elseif (is_array($userParam)) {
$nameParam = $userParam['name'];
if (is_array($nameParam) && isset($nameParam['firstName']) && isset($nameParam['lastName'])) {
$name = $nameParam['firstName'] . ' ' . $nameParam['lastName'];
}
}
$email = $oauth2->getUserEmail($accessToken);
@@ -1781,6 +1747,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state['success']['query'] = URLParser::unparseQuery($query);
$state['success'] = URLParser::unparse($state['success']);
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
@@ -1811,23 +1779,16 @@ 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 ($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('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('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')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$protocol = $request->getProtocol();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1852,20 +1813,12 @@ App::get('/v1/account/tokens/oauth2/:provider')
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$redirectBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$redirectBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$redirectBase .= ':' . $port;
}
if (empty($success)) {
$success = $redirectBase . $oauthDefaultSuccess;
$success = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $redirectBase . $oauthDefaultFailure;
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1907,7 +1860,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 ($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('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('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')
@@ -2008,16 +1961,7 @@ App::post('/v1/account/tokens/magic-url')
$dbForProject->purgeCachedDocument('users', $user->getId());
if (empty($url)) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$port = $request->getPort();
$callbackBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$url = $callbackBase . '/console/auth/magic-url';
$url = $request->getProtocol() . '://' . $request->getHostname() . '/console/auth/magic-url';
}
$url = Template::parseURL($url);
@@ -3202,7 +3146,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 ($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'])
->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'])
->inject('request')
->inject('response')
->inject('user')
@@ -3469,7 +3413,7 @@ App::post('/v1/account/verification')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{userId}')
->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
->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
->inject('request')
->inject('response')
->inject('project')
+1 -2
View File
@@ -2958,7 +2958,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$collection->getAttribute('attributes'),
$dbForProject->getAdapter()->getMaxIndexLength(),
$dbForProject->getAdapter()->getInternalIndexesKeys(),
$dbForProject->getAdapter()->getSupportForIndexArray(),
);
if (!$validator->isValid($index)) {
@@ -4310,7 +4309,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
}
$data['$id'] = $documentId;
$data['$permissions'] = $permissions ?? [];
$data['$permissions'] = $permissions;
$newDocument = new Document($data);
$operations = 0;
+4 -14
View File
@@ -523,16 +523,11 @@ 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, ?Publisher $publisherRedis, Response $response) {
->action(function (string $name, int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'databases');
$size = $isRedisFallback
? $publisherRedis->getQueueSize(new Queue($name))
: $publisher->getQueueSize(new Queue($name));
$size = $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}.");
@@ -660,16 +655,11 @@ 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, ?Publisher $publisherRedis, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$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));
$size = $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}.");
+3 -3
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,
+2 -2
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([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('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('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)
+4 -26
View File
@@ -18,7 +18,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Buckets;
use Appwrite\Utopia\Database\Validator\Queries\Files;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -954,20 +953,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
// NOTE: this is only for the sdk generator and is not used in the action below and is utilised in `resources.php` for `resourceToken`.
->param('token', '', new Text(512), 'File token for accessing this file.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('resourceToken')
->inject('deviceForFiles')
->inject('deviceForLocal')
->inject('project')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Request $request, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal, Document $project) {
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -988,7 +984,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
@@ -1040,12 +1035,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
}
$startTime = \microtime(true);
$source = $deviceForFiles->read($path);
$downloadTime = \microtime(true) - $startTime;
if (!empty($cipher)) { // Decrypt
$source = OpenSSL::decrypt(
$source,
@@ -1057,8 +1048,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
);
}
$decryptionTime = \microtime(true) - $startTime - $downloadTime;
switch ($algorithm) {
case Compression::ZSTD:
$compressor = new Zstd();
@@ -1070,8 +1059,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
break;
}
$decompressionTime = \microtime(true) - $startTime - $downloadTime - $decryptionTime;
try {
$image = new Image($source);
} catch (ImagickException $e) {
@@ -1102,12 +1089,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$data = $image->output($output, $quality);
$renderingTime = \microtime(true) - $startTime - $downloadTime - $decryptionTime - $decompressionTime;
$totalTime = \microtime(true) - $startTime;
Console::info("File preview rendered,project=" . $project->getId() . ",bucket=" . $bucketId . ",file=" . $file->getId() . ",uri=" . $request->getURI() . ",total=" . $totalTime . ",rendering=" . $renderingTime . ",decryption=" . $decryptionTime . ",decompression=" . $decompressionTime . ",download=" . $downloadTime);
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
//Do not update transformedAt if it's a console user
@@ -1159,7 +1140,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1177,10 +1158,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid && !$isToken) {
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
@@ -1320,7 +1300,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1338,10 +1317,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid && !$isToken) {
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
+2 -2
View File
@@ -11,7 +11,6 @@ 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;
@@ -51,6 +50,7 @@ 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 ($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('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('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')
-2
View File
@@ -649,8 +649,6 @@ 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,
+3 -3
View File
@@ -4,7 +4,6 @@ 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;
@@ -54,6 +53,7 @@ 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;
@@ -426,8 +426,8 @@ App::get('/v1/vcs/github/authorize')
type: MethodType::WEBAUTH,
hide: true,
))
->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'])
->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'])
->inject('request')
->inject('response')
->inject('project')
+50 -34
View File
@@ -49,13 +49,14 @@ 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);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -117,11 +118,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
/**
* Set projectId to update the Error hook logger, since x-appwrite-project is not available when executing custom domain function
*/
$log->addTag('projectId', $project->getId());
}
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
@@ -791,14 +787,13 @@ App::init()
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('log')
->inject('console')
->inject('project')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('locale')
->inject('localeCodes')
->inject('platforms')
->inject('clients')
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
@@ -809,9 +804,7 @@ App::init()
->inject('previewHostname')
->inject('devKey')
->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, 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) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey) {
/*
* Appwrite Router
*/
@@ -819,7 +812,7 @@ App::init()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -943,9 +936,42 @@ App::init()
$locale->setDefault($localeParam);
}
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
$refDomainOrigin = 'localhost';
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
} elseif (!empty($origin)) {
// Auto-allow domains with linked rule
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
} else {
$rule = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$origin]),
Query::limit(1)
])
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
$refDomainOrigin = $origin;
}
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
@@ -1017,7 +1043,7 @@ App::init()
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $httpReferrerSafe)
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true');
if (!$devKey->isEmpty()) {
@@ -1030,16 +1056,14 @@ 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($platforms);
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (
$devKey->isEmpty()
&& !empty($origin)
&& !$originValidator->isValid($origin)
!$originValidator->isValid($origin)
&& $devKey->isEmpty()
&& \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', ''))
&& \parse_url($httpReferrerSafe, PHP_URL_HOST) === 'localhost'
) {
throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription());
}
@@ -1050,7 +1074,6 @@ App::options()
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('log')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
@@ -1063,7 +1086,7 @@ App::options()
->inject('project')
->inject('devKey')
->inject('apiKey')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) {
/*
* Appwrite Router
*/
@@ -1071,7 +1094,7 @@ App::options()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1244,12 +1267,7 @@ App::error()
$log->addTag('url', $request->getURI());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$tags = $log->getTags();
if (!isset($tags['projectId'])) {
$log->addTag('projectId', $project->getId());
}
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
@@ -1367,7 +1385,6 @@ App::get('/robots.txt')
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('log')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
@@ -1378,7 +1395,7 @@ App::get('/robots.txt')
->inject('isResourceBlocked')
->inject('previewHostname')
->inject('apiKey')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -1386,7 +1403,7 @@ App::get('/robots.txt')
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1400,7 +1417,6 @@ App::get('/humans.txt')
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('log')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
@@ -1411,7 +1427,7 @@ App::get('/humans.txt')
->inject('isResourceBlocked')
->inject('previewHostname')
->inject('apiKey')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -1419,7 +1435,7 @@ App::get('/humans.txt')
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
+26
View File
@@ -129,6 +129,32 @@ App::get('/v1/mock/tests/general/oauth2/failure')
]);
});
App::patch('/v1/mock/functions-v2')
->desc('Update Function Version to V2 (outdated code syntax)')
->groups(['mock', 'api', 'functions'])
->label('scope', 'functions.write')
->label('docs', false)
->param('functionId', '', new UID(), 'Function ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Response $response, Database $dbForProject) {
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('version', 'v2'));
$response->noContent();
});
App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
+5 -14
View File
@@ -22,7 +22,6 @@ use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -564,7 +563,7 @@ App::init()
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$timestamp = 60 * 60 * 24 * 180; // Temporarily increase the TTL to 180 day to ensure files in the cache are still fetched.
$timestamp = 60 * 60 * 24 * 30;
$data = $cache->load($key, $timestamp);
if (!empty($data) && !$cacheLog->isEmpty()) {
@@ -576,6 +575,7 @@ App::init()
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
@@ -797,12 +797,6 @@ App::shutdown()
}
if (!empty($queueForDatabase->getType())) {
Console::info("Triggering database event: \n" . \json_encode([
'projectId' => $project->getId(),
'databaseId' => $queueForDatabase->getDatabase()?->getId(),
'collectionId' => $queueForDatabase->getCollection()?->getId(),
'documentId' => $queueForDatabase->getDocument()?->getId(),
]));
$queueForDatabase->trigger();
}
@@ -830,10 +824,6 @@ App::shutdown()
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$key = $request->cacheIdentifier();
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
@@ -851,11 +841,12 @@ App::shutdown()
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
$cache->save($key, $data['payload']);
}
if ($signature !== $cacheLog->getAttribute('signature')) {
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$cache->save($key, $data['payload']);
}
}
+1 -2
View File
@@ -36,8 +36,7 @@ App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
if (!App::isProduction()) {
// Allow specific domains to skip public domain validation in dev environment
// Useful for existing tests involving webhooks
PublicDomain::allow(['request-catcher-sms']);
PublicDomain::allow(['request-catcher-webhook']);
PublicDomain::allow(['request-catcher']);
}
$register->set('logger', function () {
// Register error logger
+32 -85
View File
@@ -19,7 +19,6 @@ 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;
@@ -80,15 +79,9 @@ 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']);
@@ -128,11 +121,11 @@ App::setResource('queueForCertificates', function (Publisher $publisher) {
App::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('platforms', function (Request $request, Document $console, Document $project) {
App::setResource('clients', function ($request, $console, $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
'name' => 'Current Host',
'type' => Platform::TYPE_WEB,
'type' => Origin::CLIENT_TYPE_WEB,
'hostname' => $request->getHostname(),
], Document::SET_TYPE_APPEND);
@@ -145,32 +138,39 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
}
$console->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_WEB,
'type' => Origin::CLIENT_TYPE_WEB,
'name' => $hostname,
'hostname' => $hostname,
], Document::SET_TYPE_APPEND);
}
// 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);
/**
* 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'];
}
}
return [
...$console->getAttribute('platforms', []),
...$project->getAttribute('platforms', []),
];
return \array_unique($clients);
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
@@ -222,9 +222,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
if (APP_MODE_ADMIN !== $mode) {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
@@ -234,6 +232,8 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
} else {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
}
if (
@@ -264,11 +264,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', $jwtUserId);
} else {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
$user = $dbForProject->getDocument('users', $jwtUserId);
}
$jwtSessionId = $payload['sessionId'] ?? '';
@@ -948,52 +944,3 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
}
return new Document([]);
}, ['project', 'dbForProject', 'request']);
App::setResource('httpReferrer', function (Request $request): string {
$referrer = $request->getReferer();
return $referrer;
}, ['request']);
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);
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
// Safe if route is publicly accessible
$route = $utopia->getRoute();
if ($route->getLabel('origin', false)) {
return $referrer;
}
// Safe if added as web platform
$originValidator = new Origin($platforms);
if ($originValidator->isValid($request->getOrigin($httpReferrer))) {
return $referrer;
}
// Safe if rule with same project ID exists
if (!empty($origin)) {
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
} else {
$rule = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$origin]),
Query::limit(1)
])
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
return $referrer;
}
}
// Unsafe; Localhost is always safe for ease of local development
$origin = 'localhost';
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
return $referrer;
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
+3 -3
View File
@@ -532,7 +532,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
}
$timelimit = $app->getResource('timelimit');
$platforms = $app->getResource('platforms');
$console = $app->getResource('console'); /** @var Document $console */
$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($platforms);
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
+1 -1
View File
@@ -864,7 +864,7 @@ $image = $this->getParam('image', '');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.7.22
image: openruntimes/executor:0.7.20
networks:
- appwrite
- runtimes
-8
View File
@@ -247,18 +247,10 @@ 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']);
+3 -3
View File
@@ -52,7 +52,7 @@
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "dev-validate-create-index-array as 0.71.10",
"utopia-php/database": "0.71.*",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dsn": "0.2.1",
@@ -62,12 +62,12 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.18.*",
"utopia-php/migration": "0.11.*",
"utopia-php/migration": "0.10.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.11.0",
"utopia-php/queue": "dev-feat-swoole-amqp-adapter as 0.11.1",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
Generated
+215 -209
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b7f645a2dfd3313b7202189c133ea954",
"content-hash": "783e4b89e54e4d58f3c177c1a700da7c",
"packages": [
{
"name": "adhocore/jwt",
@@ -68,17 +68,110 @@
"time": "2025-02-18T01:00:50+00:00"
},
{
"name": "appwrite/appwrite",
"version": "15.0.0",
"name": "appwrite-labs/php-amqplib",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf"
"url": "https://github.com/appwrite-labs/php-amqplib.git",
"reference": "30d709df7510e784d16ad7a7f817d26fe79615a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/deb97b62e0abed8a4fd5c5d48e77365cf89867cf",
"reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf",
"url": "https://api.github.com/repos/appwrite-labs/php-amqplib/zipball/30d709df7510e784d16ad7a7f817d26fe79615a0",
"reference": "30d709df7510e784d16ad7a7f817d26fe79615a0",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-sockets": "*",
"php": "^7.2||^8.0",
"phpseclib/phpseclib": "^2.0|^3.0"
},
"conflict": {
"php": "7.4.0 - 7.4.1"
},
"replace": {
"php-amqplib/php-amqplib": "self.version",
"videlalvaro/php-amqplib": "self.version"
},
"require-dev": {
"ext-curl": "*",
"nategood/httpful": "^0.2.20",
"phpunit/phpunit": "^7.5|^9.5",
"squizlabs/php_codesniffer": "^3.6",
"swoole/ide-helper": "^5.0"
},
"suggest": {
"ext-swoole": "For Swoole coroutine support"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"PhpAmqpLib\\": "PhpAmqpLib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Appwrite Labs",
"email": "team@appwrite.io",
"role": "Fork Maintainer"
},
{
"name": "Alvaro Videla",
"role": "Original Maintainer"
},
{
"name": "Raúl Araya",
"email": "nubeiro@gmail.com",
"role": "Maintainer"
},
{
"name": "Luke Bakken",
"email": "luke@bakken.io",
"role": "Maintainer"
},
{
"name": "Ramūnas Dronga",
"email": "github@ramuno.lt",
"role": "Maintainer"
}
],
"description": "Fork of php-amqplib with Swoole coroutine support. A pure PHP implementation of the AMQP protocol tested against RabbitMQ.",
"homepage": "https://github.com/appwrite-labs/php-amqplib/",
"keywords": [
"async",
"coroutine",
"message",
"queue",
"rabbitmq",
"swoole"
],
"support": {
"source": "https://github.com/appwrite-labs/php-amqplib/tree/0.1.0"
},
"time": "2025-06-13T21:45:04+00:00"
},
{
"name": "appwrite/appwrite",
"version": "11.1.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"shasum": ""
},
"require": {
@@ -104,10 +197,10 @@
"support": {
"email": "team@appwrite.io",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
"source": "https://github.com/appwrite/sdk-for-php/tree/15.0.0",
"source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0",
"url": "https://appwrite.io/support"
},
"time": "2025-05-18T09:47:10+00:00"
"time": "2024-06-26T07:03:23+00:00"
},
{
"name": "appwrite/php-clamav",
@@ -1113,16 +1206,16 @@
},
{
"name": "open-telemetry/api",
"version": "1.4.0",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/api.git",
"reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7"
"reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7",
"reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/4e3bb38e069876fb73c2ce85c89583bf2b28cd86",
"reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86",
"shasum": ""
},
"require": {
@@ -1142,7 +1235,7 @@
]
},
"branch-alias": {
"dev-main": "1.4.x-dev"
"dev-main": "1.1.x-dev"
}
},
"autoload": {
@@ -1179,7 +1272,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-06-19T23:36:51+00:00"
"time": "2025-05-07T12:32:21+00:00"
},
{
"name": "open-telemetry/context",
@@ -1242,16 +1335,16 @@
},
{
"name": "open-telemetry/exporter-otlp",
"version": "1.3.2",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/exporter-otlp.git",
"reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2"
"reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/196f3a1dbce3b2c0f8110d164232c11ac00ddbb2",
"reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2",
"url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/8b3ca1f86d01429c73b407bf1a2075d9c187001e",
"reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e",
"shasum": ""
},
"require": {
@@ -1302,7 +1395,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-06-16T00:24:51+00:00"
"time": "2025-05-21T12:02:20+00:00"
},
{
"name": "open-telemetry/gen-otlp-protobuf",
@@ -1369,22 +1462,22 @@
},
{
"name": "open-telemetry/sdk",
"version": "1.6.0",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/sdk.git",
"reference": "1c0371794e4c0700afd4a9d4d8511cb5e3f78e6a"
"reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/1c0371794e4c0700afd4a9d4d8511cb5e3f78e6a",
"reference": "1c0371794e4c0700afd4a9d4d8511cb5e3f78e6a",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/cd0d7367599717fc29e04eb8838ec061e6c2c657",
"reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657",
"shasum": ""
},
"require": {
"ext-json": "*",
"nyholm/psr7-server": "^1.1",
"open-telemetry/api": "~1.4.0",
"open-telemetry/api": "~1.0 || ~1.1",
"open-telemetry/context": "^1.0",
"open-telemetry/sem-conv": "^1.0",
"php": "^8.1",
@@ -1407,10 +1500,6 @@
"type": "library",
"extra": {
"spi": {
"OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig",
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig"
],
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager"
]
@@ -1459,20 +1548,20 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-06-19T23:36:51+00:00"
"time": "2025-05-22T02:33:34+00:00"
},
{
"name": "open-telemetry/sem-conv",
"version": "1.32.1",
"version": "1.32.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/sem-conv.git",
"reference": "94daa85ea61a8e2b7e1b0af6be0e875bedda7c22"
"reference": "16585cc0dbc3032a318e274043454679430d2ebf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/94daa85ea61a8e2b7e1b0af6be0e875bedda7c22",
"reference": "94daa85ea61a8e2b7e1b0af6be0e875bedda7c22",
"url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/16585cc0dbc3032a318e274043454679430d2ebf",
"reference": "16585cc0dbc3032a318e274043454679430d2ebf",
"shasum": ""
},
"require": {
@@ -1516,7 +1605,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-06-24T02:32:27+00:00"
"time": "2025-05-05T03:58:53+00:00"
},
{
"name": "paragonie/constant_time_encoding",
@@ -1635,87 +1724,6 @@
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "php-amqplib/php-amqplib",
"version": "v3.7.3",
"source": {
"type": "git",
"url": "https://github.com/php-amqplib/php-amqplib.git",
"reference": "9f50fe69a9f1a19e2cb25596a354d705de36fe59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/9f50fe69a9f1a19e2cb25596a354d705de36fe59",
"reference": "9f50fe69a9f1a19e2cb25596a354d705de36fe59",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-sockets": "*",
"php": "^7.2||^8.0",
"phpseclib/phpseclib": "^2.0|^3.0"
},
"conflict": {
"php": "7.4.0 - 7.4.1"
},
"replace": {
"videlalvaro/php-amqplib": "self.version"
},
"require-dev": {
"ext-curl": "*",
"nategood/httpful": "^0.2.20",
"phpunit/phpunit": "^7.5|^9.5",
"squizlabs/php_codesniffer": "^3.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"PhpAmqpLib\\": "PhpAmqpLib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Alvaro Videla",
"role": "Original Maintainer"
},
{
"name": "Raúl Araya",
"email": "nubeiro@gmail.com",
"role": "Maintainer"
},
{
"name": "Luke Bakken",
"email": "luke@bakken.io",
"role": "Maintainer"
},
{
"name": "Ramūnas Dronga",
"email": "github@ramuno.lt",
"role": "Maintainer"
}
],
"description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
"homepage": "https://github.com/php-amqplib/php-amqplib/",
"keywords": [
"message",
"queue",
"rabbitmq"
],
"support": {
"issues": "https://github.com/php-amqplib/php-amqplib/issues",
"source": "https://github.com/php-amqplib/php-amqplib/tree/v3.7.3"
},
"time": "2025-02-18T20:11:13+00:00"
},
{
"name": "php-http/discovery",
"version": "1.20.0",
@@ -1878,16 +1886,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.46",
"version": "3.0.45",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
"reference": "bd81b90d5963c6b9d87de50357585375223f4dd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/bd81b90d5963c6b9d87de50357585375223f4dd8",
"reference": "bd81b90d5963c6b9d87de50357585375223f4dd8",
"shasum": ""
},
"require": {
@@ -1968,7 +1976,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.45"
},
"funding": [
{
@@ -1984,7 +1992,7 @@
"type": "tidelift"
}
],
"time": "2025-06-26T16:29:55+00:00"
"time": "2025-06-22T22:54:43+00:00"
},
{
"name": "psr/container",
@@ -2327,20 +2335,21 @@
},
{
"name": "ramsey/uuid",
"version": "4.9.0",
"version": "4.8.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0"
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
@@ -2399,9 +2408,9 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.0"
"source": "https://github.com/ramsey/uuid/tree/4.8.1"
},
"time": "2025-06-25T14:20:11+00:00"
"time": "2025-06-01T06:28:46+00:00"
},
{
"name": "spomky-labs/otphp",
@@ -2547,16 +2556,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.3.1",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64"
"reference": "57e4fb86314015a695a750ace358d07a7e37b8a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64",
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64",
"url": "https://api.github.com/repos/symfony/http-client/zipball/57e4fb86314015a695a750ace358d07a7e37b8a9",
"reference": "57e4fb86314015a695a750ace358d07a7e37b8a9",
"shasum": ""
},
"require": {
@@ -2568,7 +2577,6 @@
},
"conflict": {
"amphp/amp": "<2.5",
"amphp/socket": "<1.1",
"php-http/discovery": "<1.15",
"symfony/http-foundation": "<6.4"
},
@@ -2581,6 +2589,7 @@
"require-dev": {
"amphp/http-client": "^4.2.1|^5.0",
"amphp/http-tunnel": "^1.0|^2.0",
"amphp/socket": "^1.1",
"guzzlehttp/promises": "^1.4|^2.0",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
@@ -2622,7 +2631,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.3.1"
"source": "https://github.com/symfony/http-client/tree/v7.3.0"
},
"funding": [
{
@@ -2638,7 +2647,7 @@
"type": "tidelift"
}
],
"time": "2025-06-28T07:58:39+00:00"
"time": "2025-05-02T08:23:16+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -2960,16 +2969,16 @@
},
{
"name": "tbachert/spi",
"version": "v1.0.5",
"version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/Nevay/spi.git",
"reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002"
"reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002",
"reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002",
"url": "https://api.github.com/repos/Nevay/spi/zipball/506a79c98e1a51522e76ee921ccb6c62d52faf3a",
"reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a",
"shasum": ""
},
"require": {
@@ -2987,7 +2996,7 @@
"extra": {
"class": "Nevay\\SPI\\Composer\\Plugin",
"branch-alias": {
"dev-main": "1.0.x-dev"
"dev-main": "0.2.x-dev"
},
"plugin-optional": true
},
@@ -3006,9 +3015,9 @@
],
"support": {
"issues": "https://github.com/Nevay/spi/issues",
"source": "https://github.com/Nevay/spi/tree/v1.0.5"
"source": "https://github.com/Nevay/spi/tree/v1.0.3"
},
"time": "2025-06-29T15:42:06+00:00"
"time": "2025-04-02T19:38:14+00:00"
},
{
"name": "thecodingmachine/safe",
@@ -3493,16 +3502,16 @@
},
{
"name": "utopia-php/database",
"version": "dev-validate-create-index-array",
"version": "0.71.7",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "9e2895b92cb094436973d5f8f30d75014bf03ae6"
"reference": "aa0116b2380125907fc18c82662be8e74c54091f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/9e2895b92cb094436973d5f8f30d75014bf03ae6",
"reference": "9e2895b92cb094436973d5f8f30d75014bf03ae6",
"url": "https://api.github.com/repos/utopia-php/database/zipball/aa0116b2380125907fc18c82662be8e74c54091f",
"reference": "aa0116b2380125907fc18c82662be8e74c54091f",
"shasum": ""
},
"require": {
@@ -3543,9 +3552,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/validate-create-index-array"
"source": "https://github.com/utopia-php/database/tree/0.71.7"
},
"time": "2025-07-20T08:28:54+00:00"
"time": "2025-06-17T23:59:10+00:00"
},
{
"name": "utopia-php/detector",
@@ -3942,16 +3951,16 @@
},
{
"name": "utopia-php/messaging",
"version": "0.18.1",
"version": "0.18.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "5d1245207a61d7ca065daddad7ac5f1d5640152f"
"reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/5d1245207a61d7ca065daddad7ac5f1d5640152f",
"reference": "5d1245207a61d7ca065daddad7ac5f1d5640152f",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/c151aa5d4d475c788ca15c210b5b2017e21c41d6",
"reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6",
"shasum": ""
},
"require": {
@@ -3987,26 +3996,26 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.18.1"
"source": "https://github.com/utopia-php/messaging/tree/0.18.0"
},
"time": "2025-06-26T18:26:07+00:00"
"time": "2025-05-15T05:00:03+00:00"
},
{
"name": "utopia-php/migration",
"version": "0.11.1",
"version": "0.10.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "d528a454d5c1ed6564b2843a39ff13297bcdb1af"
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/d528a454d5c1ed6564b2843a39ff13297bcdb1af",
"reference": "d528a454d5c1ed6564b2843a39ff13297bcdb1af",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/ea1c585df7ec5f346f061a11581fc9a91679966f",
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f",
"shasum": ""
},
"require": {
"appwrite/appwrite": "15.*",
"appwrite/appwrite": "11.*",
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
@@ -4043,9 +4052,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.11.1"
"source": "https://github.com/utopia-php/migration/tree/0.10.1"
},
"time": "2025-07-11T13:46:37+00:00"
"time": "2025-05-26T15:29:19+00:00"
},
{
"name": "utopia-php/orchestration",
@@ -4254,21 +4263,21 @@
},
{
"name": "utopia-php/queue",
"version": "0.11.0",
"version": "dev-feat-swoole-amqp-adapter",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "06b5ced0eaed2ecc6aab6d8e1b4d96bff37a1ce5"
"reference": "baa8c074ce1d73769cab61d2a1c2c4a9e4f97896"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/06b5ced0eaed2ecc6aab6d8e1b4d96bff37a1ce5",
"reference": "06b5ced0eaed2ecc6aab6d8e1b4d96bff37a1ce5",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/baa8c074ce1d73769cab61d2a1c2c4a9e4f97896",
"reference": "baa8c074ce1d73769cab61d2a1c2c4a9e4f97896",
"shasum": ""
},
"require": {
"appwrite-labs/php-amqplib": "0.1.0",
"php": ">=8.3",
"php-amqplib/php-amqplib": "^3.7",
"utopia-php/cli": "0.15.*",
"utopia-php/fetch": "0.4.*",
"utopia-php/framework": "0.33.*",
@@ -4314,9 +4323,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.11.0"
"source": "https://github.com/utopia-php/queue/tree/feat-swoole-amqp-adapter"
},
"time": "2025-05-30T09:52:38+00:00"
"time": "2025-06-23T19:50:06+00:00"
},
{
"name": "utopia-php/registry",
@@ -4810,16 +4819,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.41.17",
"version": "0.41.8",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "ef5e9d952032deecffee7f825a941c40ed4c84b8"
"reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ef5e9d952032deecffee7f825a941c40ed4c84b8",
"reference": "ef5e9d952032deecffee7f825a941c40ed4c84b8",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/93ffb24b25b376ca4423e3a5caf6f916673af4b2",
"reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2",
"shasum": ""
},
"require": {
@@ -4855,9 +4864,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.17"
"source": "https://github.com/appwrite/sdk-generator/tree/0.41.8"
},
"time": "2025-07-18T08:18:48+00:00"
"time": "2025-06-18T13:20:45+00:00"
},
{
"name": "doctrine/annotations",
@@ -5084,16 +5093,16 @@
},
{
"name": "laravel/pint",
"version": "v1.24.0",
"version": "v1.22.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a"
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7",
"shasum": ""
},
"require": {
@@ -5104,10 +5113,10 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.82.2",
"illuminate/view": "^11.45.1",
"larastan/larastan": "^3.5.0",
"laravel-zero/framework": "^11.45.0",
"friendsofphp/php-cs-fixer": "^3.75.0",
"illuminate/view": "^11.44.7",
"larastan/larastan": "^3.4.0",
"laravel-zero/framework": "^11.36.1",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3.1",
"pestphp/pest": "^2.36.0"
@@ -5117,9 +5126,6 @@
],
"type": "project",
"autoload": {
"files": [
"overrides/Runner/Parallel/ProcessFactory.php"
],
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
@@ -5149,20 +5155,20 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-07-10T18:09:32+00:00"
"time": "2025-05-08T08:38:12+00:00"
},
{
"name": "matthiasmullie/minify",
"version": "1.3.75",
"version": "1.3.74",
"source": {
"type": "git",
"url": "https://github.com/matthiasmullie/minify.git",
"reference": "76ba4a5f555fd7bf4aa408af608e991569076671"
"reference": "a2593286a4135d03c6a6a9e9aeded5d41e931ce4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671",
"reference": "76ba4a5f555fd7bf4aa408af608e991569076671",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/a2593286a4135d03c6a6a9e9aeded5d41e931ce4",
"reference": "a2593286a4135d03c6a6a9e9aeded5d41e931ce4",
"shasum": ""
},
"require": {
@@ -5211,7 +5217,7 @@
],
"support": {
"issues": "https://github.com/matthiasmullie/minify/issues",
"source": "https://github.com/matthiasmullie/minify/tree/1.3.75"
"source": "https://github.com/matthiasmullie/minify/tree/1.3.74"
},
"funding": [
{
@@ -5219,7 +5225,7 @@
"type": "github"
}
],
"time": "2025-06-25T09:56:19+00:00"
"time": "2025-06-12T08:06:04+00:00"
},
{
"name": "matthiasmullie/path-converter",
@@ -5276,16 +5282,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.3",
"version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"shasum": ""
},
"require": {
@@ -5324,7 +5330,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
},
"funding": [
{
@@ -5332,7 +5338,7 @@
"type": "tidelift"
}
],
"time": "2025-07-05T12:25:42+00:00"
"time": "2025-04-29T12:36:36+00:00"
},
{
"name": "nikic/php-parser",
@@ -7258,16 +7264,16 @@
},
{
"name": "symfony/console",
"version": "v7.3.1",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "9e27aecde8f506ba0fd1d9989620c04a87697101"
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101",
"reference": "9e27aecde8f506ba0fd1d9989620c04a87697101",
"url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44",
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44",
"shasum": ""
},
"require": {
@@ -7332,7 +7338,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.1"
"source": "https://github.com/symfony/console/tree/v7.3.0"
},
"funding": [
{
@@ -7348,7 +7354,7 @@
"type": "tidelift"
}
],
"time": "2025-06-27T19:55:54+00:00"
"time": "2025-05-24T10:34:04+00:00"
},
{
"name": "symfony/filesystem",
@@ -8239,15 +8245,15 @@
],
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-validate-create-index-array",
"alias": "0.71.10",
"alias_normalized": "0.71.10.0"
"package": "utopia-php/queue",
"version": "dev-feat-swoole-amqp-adapter",
"alias": "0.11.1",
"alias_normalized": "0.11.1.0"
}
],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/database": 20
"utopia-php/queue": 20
},
"prefer-stable": false,
"prefer-lowest": false,
+6 -17
View File
@@ -214,7 +214,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:6.1.12
image: appwrite/console:6.1.2
restart: unless-stopped
networks:
- appwrite
@@ -324,8 +324,7 @@ services:
depends_on:
- redis
- mariadb
- request-catcher-sms
- request-catcher-webhook
- request-catcher
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -456,7 +455,6 @@ services:
- redis
- mariadb
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
@@ -955,7 +953,7 @@ services:
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.7.22
image: openruntimes/executor:0.7.20
restart: unless-stopped
networks:
- appwrite
@@ -978,7 +976,7 @@ services:
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES,$_APP_SITES_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_RUNTIME_VERSIONS=v5
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v5
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
@@ -1079,24 +1077,15 @@ services:
networks:
- appwrite
request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook)
request-catcher: # used mainly for dev tests
image: appwrite/requestcatcher:1.0.0
container_name: appwrite-requestcatcher-webhook
container_name: appwrite-requestcatcher
<<: *x-logging
ports:
- "9504:5000"
networks:
- appwrite
request-catcher-sms: # used mainly for dev tests (mock SMS auth secret)
image: appwrite/requestcatcher:1.0.0
container_name: appwrite-requestcatcher-sms
<<: *x-logging
ports:
- "9507:5000"
networks:
- appwrite
adminer:
image: adminer
container_name: appwrite-adminer
@@ -1,16 +0,0 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = 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
Databases databases = Databases(client);
Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {},
permissions: ["read("any")"], // (optional)
);
@@ -10,5 +10,5 @@ Databases databases = Databases(client);
DocumentList result = await databases.upsertDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: [],
documents: [], // (optional)
);
@@ -1,18 +0,0 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new 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
Databases databases = new Databases(client);
Document result = await databases.UpsertDocument(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: [object],
permissions: ["read("any")"] // optional
);
@@ -12,5 +12,5 @@ Databases databases = new Databases(client);
DocumentList result = await databases.UpsertDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: new List<object>()
documents: new List<object>() // optional
);
@@ -1,16 +0,0 @@
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)
);
@@ -10,5 +10,5 @@ const databases = new sdk.Databases(client);
const result = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents
[] // documents (optional)
);
+1 -104
View File
@@ -1,104 +1 @@
# Change Log
## 8.2.2
* Fix object comparison logic when pushing settings
* Type generation fixes:
* Dart: Fixed import casing to snake_case, removed `extends Document` and hardcoded attributes, removed unnecessary imports
* Java: Fixed indentation to 4 spaces, updated imports to `java.util.Objects`, fixed enum casing in strict mode as per [Oracle official docs](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)
* Javascript: Updated optional values formatting from `|null` to `| null`
* Kotlin: Fixed indentation to 4 spaces per [Kotlinlang official docs](https://kotlinlang.org/docs/coding-conventions.html#indentation)
* PHP: Fixed indentation to 4 spaces per [PHP Fig official docs](https://www.php-fig.org/psr/psr-2/)
* Swift: Fixed indentation to 4 spaces, improved `decodeIfPresent` usage for optionals, added missing `public` to `init` method
* Typescript: Fixed indentation to 4 spaces per [Typescript coding guidelines](https://github.com/microsoft/TypeScript/wiki/Coding-guidelines)
## 8.2.1
* Added `--with-variables` option to the Sites command for adding/updating environment variables
* Fixed Functions environment variables not being pushed with `--with-variables`
* Removed `awaitPools` when wiping old variables
> **Note:** Storing environment variables in the `vars` attribute of `appwrite.json` is now deprecated due to security risks. Variables are now synced directly from the `.env` file in the root directory of the functions or sites folder.
## 8.2.0
* Add `encrypt` attribute support
* Add improved warnings on attribute recreation and deletion
* Fix `null` parsing error when using create attribute command
* Type generation fixes and improvements:
* Add `--strict` / `-s` flag to `appwrite types` command to generate types in strict mode. This automatically converts the casing of attributes to match the language's naming conventions
* Add automatic package import to `dart` language which uses package detection to import the correct package
* Add `Document` class extension to generated types in `dart` and `js` language to support internal attributes like `$id` and `$collectionId` etc.
* Add proper enum support to `js` language
* Fix indentation in `java`, `kotlin` and `swift` to use 2 spaces instead of 4 for consistency across all languages
* Fix doc comments to use correct syntax in various languages (for eg. `///` instead of `/*`)
* Update enums in `dart` to use lowerCamelCase in `strict` mode as per [constant_identifier_names](https://dart.dev/tools/diagnostics/constant_identifier_names?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=constant_identifier_names)
## 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
* Update `init` command to clear previous configuration in `appwrite.json`
* Update localConfig to store multi-region endpoint
* Fix throw error when creating unknown attribute instead of timing out
* Fix equal comparison of large numbers and BigNumber instances using proper equality checks
* Fix duplication of reasons when comparing localConfig with remoteConfig
* Fix `firstOrNull()` to `firstOrNull` in types generation for dart
* Refactor to use `isCloud()` method consistently
## 8.0.2
* Add Type generation fixes:
* Properly handle enum attributes in dart, java and kotlin
* Fix initialisation of null attributes in dart's fromMap method
* Fix relationships and enums in swift
## 8.0.1
* Add `resourceId` and `resourceType` attributes to `createRedirectRule`
* Add `providerReference` to vcs command for getting repository contents
* Add warning comment to `bulk updateDocuments` method
* Fix type generation for enums in Typescript and PHP language
## 8.0.0
* Add `types` command to generate language specific typings for collections. Currently supports - `php`, `swift`, `dart`, `js`, `ts`, `kotlin` and `java`
* Update bulk operation docs to include experiment feature warnings
* Remove assistant service and commands
## 7.0.0
* Add `sites` command
* Add `tokens` command
* Add `devKeys` support to `projects` command
* Add `init site`, `pull site` and `push site` commands
* Add bulk operation methods like `createDocuments`, `deleteDocuments` etc.
* Add new upsert methods: `upsertDocument` and `upsertDocuments`
* Update GET requests to not include content-type header
## 6.2.3
* Fix hot swapping error in `python-ml` function
## 6.2.2
* Fix GitHub builds by adding `qemu-system` package
* Fix attribute creation timed out
## 6.2.1
* Add `listOrganizations` method to `organizations` service and fix init project command
## 6.2.0
* Add specifications support to CLI
* Update package version
* Fix: Missed specifications param when updating a function
# Change Log
-8
View File
@@ -1,13 +1,5 @@
# Change Log
## 16.1.0
* Add `gif` support to `ImageFormat` enum
* Add support for `dart38` and `flutter332` runtimes
* Add `upsertDocument()` method to Databases service
* Fix `convertTo()` method in `Document` and `Preferences` models to correctly accept `Map<String, dynamic>`
* Fix `upsertDocuments()` method to have `documents` attribute as required parameter
## 16.0.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
+1 -63
View File
@@ -1,63 +1 @@
# Change Log
## 0.14.0
* Refactor from Newtonsoft.Json to System.Text.Json for serialization/deserialization
* Update package dependencies in `Package.csproj.twig`
* Migrate all serialization/deserialization logic in `Client.cs.twig`, `Query.cs.twig`, and `Extensions.cs.twig`
* Update model attributes from `[JsonProperty]` to `[JsonPropertyName]` in `Model.cs.twig`
* Create new `ObjectToInferredTypesConverter.cs.twig` for proper object type handling
* Replace `JsonConverter` with `JsonConverter<object>` in `ValueClassConverter.cs.twig`
* Update error handling to use `JsonDocument` instead of `JObject`
## 0.13.0
* 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`
* Update 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
* Remove `search` param from `listExecutions` method
## 0.12.0
* fix: remove content-type from GET requests by @loks0n in https://github.com/appwrite/sdk-for-dotnet/pull/59
* update: min and max are not optional in methods like `UpdateIntegerAttribute` etc.
* chore: regenerate sdk by @ChiragAgg5k in https://github.com/appwrite/sdk-for-dotnet/pull/60
* chore: fix build error by @ChiragAgg5k in https://github.com/appwrite/sdk-for-dotnet/pull/61
## 0.11.0
* Add new push message parameters by @abnegate in https://github.com/appwrite/sdk-for-dotnet/pull/56
## 0.10.0
* fix: chunk upload by @byawitz in https://github.com/appwrite/sdk-for-dotnet/pull/52
## 0.9.0
* Support for Appwrite 1.6
* Added `key` attribute to `Runtime` response model.
* Added `buildSize` attribute to `Deployments` response model.
* Added `scheduledAt` attribute to `Executions` response model.
* Added `scopes` attribute to `Functions` response model.
* Added `specifications` attribute to `Functions` response model.
* Added new response model for `Specifications`.
* Added new response model for `Builds`.
* Added `createJWT()` : Enables creating a JWT using the `userId`.
* Added `listSpecifications()`: Enables listing available runtime specifications.
* Added `deleteExecution()` : Enables deleting executions.
* Added `updateDeploymentBuild()`: Enables cancelling a deployment.
* Added `scheduledAt` parameter to `createExecution()`: Enables creating a delayed execution
#### Breaking changes
You can find the new syntax for breaking changes in the [Appwrite API references](https://appwrite.io/docs/references). Select version `1.6.x`.
* Removed `otp` parameter from `deleteMFAAuthenticator`.
* Added `scopes` parameter for create/update function.
* Renamed `templateBranch` to `templateVersion` in `createFunction()`.
* Renamed `downloadDeployment()` to `getDeploymentDownload()`
> **Please note: This version is compatible with Appwrite 1.6 and later only. If you do not update your Appwrite SDK, old SDKs will not break your app. Appwrite APIs are backwards compatible.**
# Change Log
+1 -12
View File
@@ -1,20 +1,9 @@
# Change Log
## 17.0.2
* Add `gif` support to `ImageFormat` enum
* Fix `convertTo()` method in `Document` and `Preferences` models to correctly accept `Map<String, dynamic>`
## 17.0.1
* Fix `devKeys` support by conditionally including credentials during requests
## 17.0.0
## 16.1.1
* Update `flutter_web_auth_2` dependency to version 4.1.0
* Update `auth.html` example in README.md to align with `flutter_web_auth_2` documentation
* Breaking changes:
* Minimum iOS version supported is now 17.4 due to the updated requirements of `flutter_web_auth_2` version 4.1.0
## 16.1.0
+3 -58
View File
@@ -1,68 +1,13 @@
# 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
## 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`.
* Removes `search` param from `listExecutions` method
-5
View File
@@ -1,10 +1,5 @@
# Change log
## 0.10.1
* Fix URL based methods like `getFileViewURL`, `getFilePreviewURL` etc. by adding the missing `projectId` to searchParams
* Add `gif` to ImageFormat enum
## 0.10.0
* Add generate file URL methods like`getFilePreviewURL`, `getFileViewURL` etc.
-10
View File
@@ -54,16 +54,6 @@ class Database extends Event
return $this;
}
/**
* Returns set database for this event.
*
* @return null|Document
*/
public function getDatabase(): ?Document
{
return $this->database;
}
/**
* Set the collection for this database event.
*
+17 -20
View File
@@ -226,7 +226,7 @@ class Mapper
];
if (!$rule['required']) {
$fields[$escapedKey]['defaultValue'] = $rule['default'] ?? null;
$fields[$escapedKey]['defaultValue'] = $rule['default'];
}
}
@@ -267,51 +267,48 @@ class Mapper
}
switch ((!empty($validator)) ? $validator::class : '') {
case 'Appwrite\Auth\Validator\Password':
case 'Appwrite\Event\Validator\Event':
case 'Appwrite\Event\Validator\FunctionEvent':
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Network\Validator\Redirect':
case 'Appwrite\Network\Validator\DNS':
case 'Appwrite\Network\Validator\Origin':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Utopia\Database\Validator\Key':
case 'Utopia\Database\Validator\UID':
case 'Utopia\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Event\Validator\Event':
case 'Appwrite\Event\Validator\FunctionEvent':
case 'Utopia\Validator\HexColor':
case 'Utopia\Validator\Host':
case 'Utopia\Validator\IP':
case 'Utopia\Database\Validator\Key':
case 'Utopia\Validator\Origin':
case 'Appwrite\Auth\Validator\Password':
case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
case 'Utopia\Validator\URL':
case 'Utopia\Validator\WhiteList':
default:
$type = Type::string();
break;
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Utopia\Database\Validator\Authorization':
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Utopia\Database\Validator\Queries\Documents':
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
case 'Utopia\Database\Validator\Permissions':
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Roles':
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
case 'Utopia\Database\Validator\Authorization':
case 'Utopia\Database\Validator\Permissions':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Queries\Documents':
case 'Utopia\Database\Validator\Roles':
$type = Type::listOf(Type::string());
break;
case 'Utopia\Validator\Boolean':
-159
View File
@@ -1,159 +0,0 @@
<?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);
}
}
+117 -80
View File
@@ -2,91 +2,150 @@
namespace Appwrite\Network\Validator;
use Appwrite\Network\Platform;
use Utopia\Validator;
use Utopia\Validator\Hostname;
class Origin extends Validator
{
protected array $hostnames = [];
protected array $schemes = [];
protected ?string $scheme = null;
protected ?string $host = null;
protected string $origin = '';
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';
/**
* Constructor
*
* @param array<\Utopia\Database\Document> $platforms
* @var array
*/
public function __construct(array $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)
{
$this->hostnames = Platform::getHostnames($platforms);
$this->schemes = Platform::getSchemes($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;
}
}
}
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 is valid.
* @param mixed $origin The Origin URI.
* Check if Origin has been allowed
* for access to the API
*
* @param mixed $origin
*
* @return bool
*/
public function isValid($origin): bool
{
$this->origin = $origin;
$this->scheme = null;
$this->host = null;
if (!is_string($origin) || empty($origin)) {
if (!is_string($origin)) {
return false;
}
$this->scheme = $this->parseScheme($origin);
$this->host = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
$scheme = \parse_url($origin, PHP_URL_SCHEME);
$host = \parse_url($origin, PHP_URL_HOST);
$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);
}
$this->host = $host;
$this->client = $scheme;
if (!empty($this->scheme) && in_array($this->scheme, $this->schemes, true)) {
if (empty($host)) {
return true;
}
return false;
}
$validator = new Hostname($this->clients);
/**
* Get Description
* @return string
*/
public function getDescription(): string
{
$platform = $this->scheme ? Platform::getNameByScheme($this->scheme) : '';
$host = $this->host ? '(' . $this->host . ')' : '';
if (empty($this->host) && empty($this->scheme)) {
return 'Invalid Origin.';
}
if (empty($platform)) {
return 'Invalid Scheme. The scheme used (' . $this->scheme . ') in the Origin (' . $this->origin . ') is not supported. If you are using a custom scheme, please change it to `appwrite-callback-<PROJECT_ID>`';
}
return 'Invalid Origin. Register your new client ' . $host . ' as a new '
. $platform . ' platform on your project console dashboard';
return $validator->isValid($host);
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
@@ -96,35 +155,13 @@ 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;
}
}
}
@@ -1,25 +0,0 @@
<?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';
}
}
-62
View File
@@ -3,10 +3,7 @@
namespace Appwrite\Platform;
use Swoole\Coroutine as Co;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Platform\Action as UtopiaAction;
@@ -19,12 +16,6 @@ class Action extends UtopiaAction
*/
protected mixed $logError;
protected array $filters = [
'subQueryKeys', 'subQueryWebhooks', 'subQueryPlatforms', 'subQueryProjectVariables', 'subQueryBlocks', 'subQueryDevKeys', // Project
'subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships', 'subQueryTargets', 'subQueryTopicTargets',// Users
'subQueryVariables', // Sites
];
/**
* Foreach Document
* Call provided callback for each document in the collection
@@ -96,57 +87,4 @@ class Action extends UtopiaAction
$latestDocument = $results[array_key_last($results)];
}
}
public function disableSubqueries()
{
$filters = $this->filters;
foreach ($filters as $filter) {
Database::addFilter(
$filter,
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return [];
}
);
}
}
/**
* Dump Log Message
*
* Logs messages to console with timestamp, method context, and project details.
* Supports multiple log types: success, error, log, warning, and info (default).
*
* @param string $method The calling method name
* @param string $log The log message
* @param string $type The log type (success, error, log, warning, info)
* @param Document|null $project The project document for context
* @param string $collectionId The collection identifier
* @return void
*/
public function dump(string $method, string $log, string $type = 'info', ?Document $project = null, string $collectionId = ''): void
{
if (empty($project)) {
$project = new Document([]);
}
switch ($type) {
case 'success':
Console::success("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
break;
case 'error':
Console::error("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
break;
case 'log':
Console::log("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
break;
case 'warning':
Console::warning("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
break;
default:
Console::info("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
}
}
}
@@ -6,7 +6,6 @@ use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -227,73 +226,6 @@ class Base extends Action
]))
);
if (!empty($commitDetails['commitHash'])) {
$domain = "commit-" . substr($commitDetails['commitHash'], 0, 16) . ".{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
]))
);
} catch (Duplicate $err) {
// Ignore, rule already exists; will be updated by builds worker
}
}
// VCS branch preview
if (!empty($providerBranch)) {
$branchPrefix = substr($providerBranch, 0, 16);
if (strlen($providerBranch) > 16) {
$remainingChars = substr($providerBranch, 16);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
}
$resourceProjectHash = substr(hash('sha256', $site->getId() . $project->getId()), 0, 7);
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
]))
);
} catch (Duplicate $err) {
// Ignore, rule already exists; will be updated by builds worker
}
}
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
@@ -57,7 +57,7 @@ class Get extends Action
->param('type', '', new WhiteList(['rules']), 'Resource type.')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -87,7 +87,7 @@ class Create extends Action
->inject('deviceForLocal')
->inject('queueForBuilds')
->inject('plan')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -62,7 +62,7 @@ class Delete extends Action
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('deviceForFunctions')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -61,7 +61,7 @@ class Get extends Action
->inject('dbForProject')
->inject('deviceForFunctions')
->inject('deviceForBuilds')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -62,7 +62,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('deviceForFunctions')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -49,7 +49,7 @@ class Get extends Action
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -59,7 +59,7 @@ class Update extends Action
->inject('project')
->inject('queueForEvents')
->inject('executor')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -75,7 +75,7 @@ class Create extends Base
->inject('project')
->inject('queueForBuilds')
->inject('gitHub')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -72,7 +72,7 @@ class Create extends Base
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('gitHub')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -57,7 +57,7 @@ class XList extends Action
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -94,7 +94,7 @@ class Create extends Base
->inject('queueForFunctions')
->inject('geodb')
->inject('executor')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -62,7 +62,7 @@ class Delete extends Base
->inject('dbForProject')
->inject('dbForPlatform')
->inject('queueForEvents')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -52,7 +52,7 @@ class Get extends Base
->param('executionId', '', new UID(), 'Execution ID.')
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -58,7 +58,7 @@ class XList extends Base
->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -114,7 +114,7 @@ class Create extends Base
->inject('dbForPlatform')
->inject('request')
->inject('gitHub')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -61,7 +61,7 @@ class Delete extends Base
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -62,7 +62,7 @@ class Update extends Base
->inject('dbForProject')
->inject('queueForEvents')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -49,7 +49,7 @@ class Get extends Base
->param('functionId', '', new UID(), 'Function ID.')
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -104,7 +104,7 @@ class Update extends Base
->inject('dbForPlatform')
->inject('gitHub')
->inject('executor')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -56,7 +56,7 @@ class XList extends Base
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -47,7 +47,7 @@ class XList extends Base
]
))
->inject('response')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(Response $response)
@@ -48,7 +48,7 @@ class XList extends Base
))
->inject('response')
->inject('plan')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(Response $response, array $plan)
@@ -63,8 +63,8 @@ class XList extends Base
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_COMPUTE_CPUS', 0);
$maxMemory = System::getEnv('_APP_COMPUTE_MEMORY', 0);
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
// Treat 0 as no limit
@@ -49,7 +49,7 @@ class Get extends Base
))
->param('templateId', '', new Text(128), 'Template ID.')
->inject('response')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $templateId, Response $response)
@@ -53,7 +53,7 @@ class XList extends Base
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
->inject('response')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(array $runtimes, array $usecases, int $limit, int $offset, Response $response)
@@ -55,7 +55,7 @@ class Get extends Base
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $functionId, string $range, Response $response, Database $dbForProject)
@@ -52,7 +52,7 @@ class XList extends Base
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $range, Response $response, Database $dbForProject)
@@ -65,7 +65,7 @@ class Create extends Base
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -57,7 +57,7 @@ class Delete extends Base
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -53,7 +53,7 @@ class Get extends Base
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $functionId, string $variableId, Response $response, Database $dbForProject)
@@ -61,7 +61,7 @@ class Update extends Base
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -53,7 +53,7 @@ class XList extends Base
->param('functionId', '', new UID(), 'Function unique ID.', false)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $functionId, Response $response, Database $dbForProject)
@@ -75,7 +75,7 @@ class Builds extends Action
->inject('log')
->inject('executor')
->inject('plan')
->callback($this->action(...));
->callback([$this, 'action']);
}
/**
@@ -209,9 +209,6 @@ class Builds extends Action
Executor $executor,
array $plan
): void {
$startTime = DateTime::now();
$durationStart = \microtime(true);
$resourceKey = match ($resource->getCollection()) {
'functions' => 'functionId',
'sites' => 'siteId',
@@ -263,8 +260,11 @@ class Builds extends Action
->setParam($resourceKey, $resource->getId())
->setParam('deploymentId', $deployment->getId());
$startTime = DateTime::now();
$durationStart = \microtime(true);
if ($deployment->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -275,7 +275,8 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$queueForRealtime
@@ -391,7 +392,7 @@ class Builds extends Action
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $deploymentId), '', $stdout, $stderr);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -444,7 +445,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 checkout -b ' . \escapeshellarg($branchName) . ' && 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 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);
@@ -524,7 +525,8 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$queueForRealtime
@@ -592,7 +594,7 @@ class Builds extends Action
]);
$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', ''));
$endpoint = $protocol . '://' . $hostname . "/v1";
// Appwrite vars
@@ -656,7 +658,7 @@ class Builds extends Action
$err = null;
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -745,13 +747,6 @@ class Builds extends Action
if ($separator !== false) {
$logs = \substr($logs, 0, $separator);
$insideSeparation = true;
$leftover = \substr($logs, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR_START}'));
$separator = \strpos($leftover, '{APPWRITE_DETECTION_SEPARATOR_END}');
if ($separator !== false) {
$logs .= \substr($leftover, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR_END}'));
$insideSeparation = false;
}
}
} else {
$separator = \strpos($logs, '{APPWRITE_DETECTION_SEPARATOR_END}');
@@ -807,7 +802,7 @@ class Builds extends Action
]);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -815,6 +810,9 @@ class Builds extends Action
throw $err;
}
$endTime = DateTime::now();
$durationEnd = \microtime(true);
$buildSizeLimit = (int)System::getEnv('_APP_COMPUTE_BUILD_SIZE_LIMIT', '2000000000');
if (isset($plan['buildSize'])) {
$buildSizeLimit = $plan['buildSize'] * 1000 * 1000;
@@ -823,6 +821,10 @@ class Builds extends Action
throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / (1000 * 1000), 2) . ' MBs.');
}
/** Update the build document */
$deployment->setAttribute('buildStartedAt', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
$deployment->setAttribute('buildEndedAt', $endTime);
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
$deployment->setAttribute('buildPath', $response['path']);
$deployment->setAttribute('buildSize', $response['size']);
$deployment->setAttribute('totalSize', $deployment->getAttribute('buildSize', 0) + $deployment->getAttribute('sourceSize', 0));
@@ -834,10 +836,18 @@ class Builds extends Action
// Separate logs for SSR detection
$detectionLogs = '';
if (\str_contains($logs, '{APPWRITE_DETECTION_SEPARATOR_START}')) {
[$logsBefore, $detectionLogsStart] = \explode('{APPWRITE_DETECTION_SEPARATOR_START}', $logs, 2);
[$detectionLogs, $logsAfter] = \explode('{APPWRITE_DETECTION_SEPARATOR_END}', $detectionLogsStart, 2);
$logs = ($logsBefore ?? '') . ($logsAfter ?? '');
$separator = \strpos($logs, '{APPWRITE_DETECTION_SEPARATOR_START}');
if ($separator !== false) {
$detectionLogs = \substr($logs, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR}'));
$separatorEnd = \strpos($detectionLogs, '{APPWRITE_DETECTION_SEPARATOR_END}');
$logs .= \substr($detectionLogs, $separatorEnd + strlen('{APPWRITE_DETECTION_SEPARATOR_END}'));
$detectionLogs = \substr($detectionLogs, 0, $separatorEnd);
$logs = \substr($logs, 0, $separator);
}
if ($resource->getCollection() === 'sites') {
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing started. \n";
}
$deployment->setAttribute('buildLogs', $logs);
@@ -856,7 +866,9 @@ class Builds extends Action
$adapter = $resource->getAttribute('adapter', '');
if (empty($adapter)) {
$resource = $dbForProject->updateDocument('sites', $resource->getId(), new Document(['adapter' => $detection->getName(), 'fallbackFile' => $detection->getFallbackFile() ?? '']));
$resource->setAttribute('adapter', $detection->getName());
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$deployment->setAttribute('adapter', $detection->getName());
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
@@ -865,24 +877,16 @@ class Builds extends Action
}
}
$deployment->setAttribute('buildLogs', $logs);
$this->afterBuildSuccess($dbForProject, $deployment);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
$this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment);
$logs = $deployment->getAttribute('buildLogs', '');
if ($resource->getCollection() === 'sites') {
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing started. \n";
$deployment->setAttribute('buildLogs', $logs);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
}
/** Screenshot site */
if ($resource->getCollection() === 'sites') {
try {
@@ -955,9 +959,8 @@ class Builds extends Action
$config['sleep'] = $framework['screenshotSleep'];
}
$browserEndpoint = Config::getParam('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
$fetchResponse = $client->fetch(
url: $browserEndpoint . '/screenshots',
url: 'http://appwrite-browser:3000/v1/screenshots',
method: 'POST',
body: $config
);
@@ -1059,7 +1062,8 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$queueForRealtime
@@ -1073,38 +1077,14 @@ class Builds extends Action
Console::success("Build id: $deploymentId created");
/** Set auto deploy */
$activateBuild = false;
if ($deployment->getAttribute('activate') === true) {
// Check if current active deployment started later than this deployment
$resource = $dbForProject->getDocument($resource->getCollection(), $resource->getId());
$currentActiveDeploymentId = $resource->getAttribute('deploymentId', '');
if (!empty($currentActiveDeploymentId)) {
$currentActiveDeployment = $dbForProject->getDocument('deployments', $currentActiveDeploymentId);
if (!$currentActiveDeployment->isEmpty()) {
$currentActiveStartTime = $currentActiveDeployment->getCreatedAt();
$deploymentStartTime = $deployment->getCreatedAt();
// Skip auto-activation if current active deployment started later than deployment that is being activated
if ($currentActiveStartTime < $deploymentStartTime) {
$activateBuild = true;
} else {
Console::info('Skipping auto-activation as current deployment is more recent');
}
}
} else {
$activateBuild = true;
}
}
if ($activateBuild) {
$resource->setAttribute('live', true);
switch ($resource->getCollection()) {
case 'functions':
$resource = $dbForProject->updateDocument('functions', $resource->getId(), new Document([
'live' => true,
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentCreatedAt' => $deployment->getCreatedAt(),
]));
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getSequence());
$resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
@@ -1118,21 +1098,19 @@ class Builds extends Action
$rulesUpdated = false;
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment, &$rulesUpdated) {
$rulesUpdated = true;
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
}, $queries);
break;
case 'sites':
$resource = $dbForProject->updateDocument('sites', $resource->getId(), new Document([
'live' => true,
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentScreenshotDark' => $deployment->getAttribute('screenshotDark', ''),
'deploymentScreenshotLight' => $deployment->getAttribute('screenshotLight', ''),
'deploymentCreatedAt' => $deployment->getCreatedAt(),
]));
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getSequence());
$resource->setAttribute('deploymentScreenshotDark', $deployment->getAttribute('screenshotDark', ''));
$resource->setAttribute('deploymentScreenshotLight', $deployment->getAttribute('screenshotLight', ''));
$resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('type', ['deployment']),
@@ -1143,10 +1121,10 @@ class Builds extends Action
];
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
}, $queries);
break;
@@ -1188,10 +1166,11 @@ class Builds extends Action
'region' => $project->getAttribute('region')
]));
} catch (Duplicate $err) {
$rule = $dbForPlatform->updateDocument('rules', $ruleId, new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
}
$queries = [
@@ -1204,25 +1183,16 @@ class Builds extends Action
];
$dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
}, $queries);
}
}
$endTime = DateTime::now();
$durationEnd = \microtime(true);
$deployment->setAttribute('buildEndedAt', $endTime);
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -1245,7 +1215,7 @@ class Builds extends Action
Console::error($th->getTraceAsString());
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
Console::info('Build has been canceled');
return;
}
@@ -1255,12 +1225,16 @@ class Builds extends Action
$message = "" . $message;
}
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_START}', '', $message);
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_END}', '', $message);
$separator = \strpos($message, '{APPWRITE_DETECTION_SEPARATOR_START}');
if ($separator !== false) {
$error = \substr($message, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR_START}'));
$message = \substr($message, 0, $separator);
$message .= "\n" . $error;
}
// Combine with previous logs if deployment got past build process
$previousLogs = '';
if (!is_null($deployment->getAttribute('buildSize', null))) {
if (!empty($deployment->getAttribute('buildEndedAt', ''))) {
$previousLogs = $deployment->getAttribute('buildLogs', '');
if (!empty($previousLogs)) {
$message = $previousLogs . "\n" . $message;
@@ -1277,7 +1251,8 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$queueForRealtime
@@ -1303,8 +1278,6 @@ class Builds extends Action
protected function sendUsage(Document $resource, Document $deployment, Document $project, StatsUsage $queue): void
{
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
switch ($deployment->getAttribute('status')) {
case 'ready':
$queue
@@ -1346,14 +1319,12 @@ class Builds extends Action
/**
* Hook to run after build success
*
* @param Realtime $queueForRealtime
* @param Database $dbForProject
* @param Document $deployment
* @return void
*/
protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment): void
protected function afterBuildSuccess(Database $dbForProject, Document &$deployment): void
{
assert($queueForRealtime instanceof Realtime);
assert($dbForProject instanceof Database);
assert($deployment instanceof Document);
}
@@ -1533,22 +1504,4 @@ class Builds extends Action
}
}
}
private function cancelDeployment(string $deploymentId, Database $dbForProject, Realtime $queueForRealtime)
{
Console::info('Build has been canceled');
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$logs = $deployment->getAttribute('buildLogs', '');
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Build has been canceled. \n";
$deployment->setAttribute('buildLogs', $logs);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
}
}
@@ -57,7 +57,7 @@ class Create extends Action
->inject('user')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $projectId, string $name, ?string $expire, Document $user, Response $response, Database $dbForPlatform)
@@ -49,7 +49,7 @@ class Delete extends Action
->param('keyId', '', new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $projectId, string $keyId, Response $response, Database $dbForPlatform)
@@ -49,7 +49,7 @@ class Get extends Action
->param('keyId', '', new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $projectId, string $keyId, Response $response, Database $dbForPlatform)
@@ -52,7 +52,7 @@ class Update extends Action
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format.')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $projectId, string $keyId, string $name, ?string $expire, Response $response, Database $dbForPlatform)
{
@@ -53,7 +53,7 @@ class XList extends Action
->param('queries', [], new DevKeys(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', DevKeys::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $projectId, ?array $queries, Response $response, Database $dbForPlatform)
@@ -66,7 +66,7 @@ class Create extends Action
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('dbForPlatform')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
@@ -71,7 +71,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
@@ -74,7 +74,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
@@ -71,7 +71,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
@@ -86,7 +86,7 @@ class Create extends Action
->inject('deviceForLocal')
->inject('queueForBuilds')
->inject('plan')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -62,7 +62,7 @@ class Delete extends Action
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('deviceForSites')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -60,7 +60,7 @@ class Get extends Action
->inject('dbForProject')
->inject('deviceForSites')
->inject('deviceForBuilds')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -65,7 +65,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('deviceForSites')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -49,7 +49,7 @@ class Get extends Action
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject)
@@ -57,7 +57,7 @@ class Update extends Action
->inject('project')
->inject('queueForEvents')
->inject('executor')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -77,7 +77,7 @@ class Create extends Base
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('gitHub')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -72,7 +72,7 @@ class Create extends Base
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('gitHub')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(
@@ -57,7 +57,7 @@ class XList extends Action
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $siteId, array $queries, string $search, Response $response, Database $dbForProject)
@@ -46,7 +46,7 @@ class XList extends Base
]
))
->inject('response')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(Response $response)
@@ -55,7 +55,7 @@ class Delete extends Base
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $siteId, string $logId, Response $response, Database $dbForProject, Event $queueForEvents)
@@ -50,7 +50,7 @@ class Get extends Base
->param('logId', '', new UID(), 'Log ID.')
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $siteId, string $logId, Response $response, Database $dbForProject)
@@ -57,7 +57,7 @@ class XList extends Base
->param('queries', [], new Logs(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
->callback([$this, 'action']);
}
public function action(string $siteId, array $queries, Response $response, Database $dbForProject)

Some files were not shown because too many files have changed in this diff Show More