Merge branch '1.8.x' into 'bump-migrations'.

This commit is contained in:
Darshan
2025-07-18 10:57:29 +05:30
78 changed files with 1023 additions and 746 deletions
+3 -2
View File
@@ -85,8 +85,9 @@ _APP_COMPUTE_MAINTENANCE_INTERVAL=600
_APP_COMPUTE_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://exc1/v1
_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_BROWSER_HOST=http://appwrite-browser:3000/v1
_APP_FUNCTIONS_RUNTIMES=node-22
_APP_SITES_RUNTIMES=static-1,node-22
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_START_TIME=12:00
_APP_MAINTENANCE_RETENTION_CACHE=2592000
+85 -2
View File
@@ -179,6 +179,7 @@ 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 +200,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
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
@@ -279,7 +280,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
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
@@ -360,3 +361,85 @@ 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 -4
View File
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.10.0',
'version' => '0.10.1',
'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.1.0',
'version' => '8.2.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -250,7 +250,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '17.0.0',
'version' => '17.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -358,7 +358,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.13.0',
'version' => '0.14.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
+86 -29
View File
@@ -1189,9 +1189,16 @@ App::get('/v1/account/sessions/oauth2/:provider')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$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 = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1216,12 +1223,20 @@ 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 = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
$success = $redirectBase . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
$failure = $redirectBase . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1251,9 +1266,14 @@ 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) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$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;
}
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1262,7 +1282,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($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1282,8 +1302,14 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function (string $projectId, string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$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;
}
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1292,7 +1318,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($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1316,15 +1342,24 @@ 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, 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();
->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();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$validateURL = new URL();
$redirect = new Redirect($platforms);
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
@@ -1351,11 +1386,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state = $defaultState;
}
if (!$validateURL->isValid($state['success'])) {
if ($devKey->isEmpty() && !$redirect->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirect->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$failure = [];
@@ -1422,13 +1457,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$name = '';
$nameOAuth = $oauth2->getUserName($accessToken);
$userParam = \json_decode($request->getParam('user'), true);
$userParam = $request->getParam('user');
if (!empty($nameOAuth)) {
$name = $nameOAuth;
} elseif (is_array($userParam)) {
$nameParam = $userParam['name'];
if (is_array($nameParam) && isset($nameParam['firstName']) && isset($nameParam['lastName'])) {
$name = $nameParam['firstName'] . ' ' . $nameParam['lastName'];
} elseif ($userParam !== null) {
$userDecoded = \json_decode($userParam, true);
if (isset($userDecoded['name']['firstName']) && isset($userDecoded['name']['lastName'])) {
$name = $userDecoded['name']['firstName'] . ' ' . $userDecoded['name']['lastName'];
}
}
$email = $oauth2->getUserEmail($accessToken);
@@ -1746,8 +1781,6 @@ 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')
@@ -1785,9 +1818,16 @@ App::get('/v1/account/tokens/oauth2/:provider')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$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 = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1812,12 +1852,20 @@ 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 = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
$success = $redirectBase . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
$failure = $redirectBase . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1960,7 +2008,16 @@ App::post('/v1/account/tokens/magic-url')
$dbForProject->purgeCachedDocument('users', $user->getId());
if (empty($url)) {
$url = $request->getProtocol() . '://' . $request->getHostname() . '/console/auth/magic-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 = Template::parseURL($url);
+8 -3
View File
@@ -967,6 +967,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
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());
@@ -987,6 +988,7 @@ 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));
}
@@ -1157,7 +1159,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());
@@ -1175,9 +1177,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
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));
}
@@ -1317,6 +1320,7 @@ 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());
@@ -1334,9 +1338,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
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));
}
-26
View File
@@ -129,32 +129,6 @@ 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'])
-1
View File
@@ -576,7 +576,6 @@ 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)) {
+3 -2
View File
@@ -214,7 +214,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:6.1.2
image: appwrite/console:6.1.12
restart: unless-stopped
networks:
- appwrite
@@ -456,6 +456,7 @@ services:
- redis
- mariadb
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
@@ -977,7 +978,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=v2,v5
- OPR_EXECUTOR_RUNTIME_VERSIONS=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
@@ -0,0 +1,18 @@
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>() // optional
documents: new List<object>()
);
@@ -0,0 +1,16 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const databases = new sdk.Databases(client);
const result = await databases.upsertDocument(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
'<DOCUMENT_ID>', // documentId
{}, // data
["read("any")"] // permissions (optional)
);
@@ -10,5 +10,5 @@ const databases = new sdk.Databases(client);
const result = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents (optional)
[] // documents
);
+31
View File
@@ -1,5 +1,36 @@
# Change Log
## 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
+63 -1
View File
@@ -1 +1,63 @@
# Change Log
# 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.**
+58 -3
View File
@@ -1,13 +1,68 @@
# Change Log
## 17.1.0
* Add `upsertDocument` method
* Add `dart-3.8` and `flutter-3.32` runtimes
* Add `gif` image format
* Update bulk operation methods to reflect warning message
* Fix file parameter handling in chunked upload method
## 17.0.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
* Add `REGION` to doc examples due to the new multi region endpoints
* Add doc examples and methods for bulk api transactions: `createDocuments`, `deleteDocuments` etc.
* Add doc examples, class and methods for new `Sites` service
* Add doc examples, class and methods for new `Tokens` service
* Add enums for `BuildRuntime `, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Add enums for `BuildRuntime`, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Updates enum for `runtimes` with Pythonml312, Dart219, Flutter327 and Flutter329
* Add `token` param to `getFilePreview` and `getFileView` for File tokens usage
* Add `queries` and `search` params to `listMemberships` method
* Removes `search` param from `listExecutions` method
* Removes `search` param from `listExecutions` method
## 16.0.0
* Fix: remove content-type from GET requests
* Update (breaking): min and max params are now optional in `updateFloatAttribute` and `updateIntegerAttribute` methods (changes their positioning in method definition)
## 15.0.1
* Remove titles from all function descriptions
* Fix typing for collection "attribute" key
* Remove unnecessary awaits and asyncs
* Ensure `AppwriteException` response is always string
## 15.0.0
* Fix: pong response & chunked upload
## 14.2.0
* Add new push message parameters
## 14.1.0
* Support updating attribute name and size
## 14.0.0
* Support for Appwrite 1.6
* Add `key` attribute to `Runtime` response model.
* Add `buildSize` attribute to `Deployments` response model
* Add `scheduledAt` attribute to `Executions` response model
* Add `scopes` attribute to `Functions` response model
* Add `specifications` attribute to `Functions` response model
* Add new response model for `Specifications`
* Add new response model for `Builds`
* Add `createJWT()` : Enables creating a JWT using the `userId`
* Add `listSpecifications()`: Enables listing available runtime specifications
* Add `deleteExecution()` : Enables deleting executions
* Add `updateDeploymentBuild()`: Enables cancelling a deployment
* Add `scheduledAt` parameter to `createExecution()`: Enables creating a delayed execution
* Breaking changes
* Remove `otp` parameter from `deleteMFAAuthenticator`.
* Add `scopes` parameter for create/update function.
* Rename `templateBranch` to `templateVersion` in `createFunction()`.
* Rename `downloadDeployment()` to `getDeploymentDownload()`
> You can find the new syntax for breaking changes in the [Appwrite API references](https://appwrite.io/docs/references). Select version `1.6.x`.
+5
View File
@@ -1,5 +1,10 @@
# 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.
+20 -17
View File
@@ -267,50 +267,53 @@ class Mapper
}
switch ((!empty($validator)) ? $validator::class : '') {
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Utopia\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
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 '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 'Utopia\Database\Validator\Authorization':
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
case 'Appwrite\Utopia\Database\Validator\Queries\Tables':
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Columns':
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\Rules':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
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\Rules':
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':
+8
View File
@@ -24,6 +24,10 @@ class Platform
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';
@@ -45,6 +49,10 @@ class Platform
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)',
];
/**
+9 -1
View File
@@ -42,7 +42,15 @@ class Origin extends Validator
$this->scheme = $this->parseScheme($origin);
$this->host = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
if (in_array($this->scheme, [Platform::SCHEME_HTTP, Platform::SCHEME_HTTPS], true)) {
$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);
}
@@ -63,8 +63,8 @@ class XList extends Base
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
$maxCpus = System::getEnv('_APP_COMPUTE_CPUS', 0);
$maxMemory = System::getEnv('_APP_COMPUTE_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
// Treat 0 as no limit
@@ -264,7 +264,7 @@ class Builds extends Action
->setParam('deploymentId', $deployment->getId());
if ($deployment->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -275,8 +275,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -392,7 +391,7 @@ class Builds extends Action
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $deploymentId), '', $stdout, $stderr);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -525,8 +524,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -658,7 +656,7 @@ class Builds extends Action
$err = null;
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -809,7 +807,7 @@ class Builds extends Action
]);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -858,9 +856,7 @@ class Builds extends Action
$adapter = $resource->getAttribute('adapter', '');
if (empty($adapter)) {
$resource->setAttribute('adapter', $detection->getName());
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$resource = $dbForProject->updateDocument('sites', $resource->getId(), new Document(['adapter' => $detection->getName(), 'fallbackFile' => $detection->getFallbackFile() ?? '']));
$deployment->setAttribute('adapter', $detection->getName());
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
@@ -959,8 +955,9 @@ class Builds extends Action
$config['sleep'] = $framework['screenshotSleep'];
}
$browserEndpoint = Config::getParam('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
$fetchResponse = $client->fetch(
url: 'http://appwrite-browser:3000/v1/screenshots',
url: $browserEndpoint . '/screenshots',
method: 'POST',
body: $config
);
@@ -1062,8 +1059,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -1077,14 +1073,38 @@ class Builds extends Action
Console::success("Build id: $deploymentId created");
/** Set auto deploy */
$activateBuild = false;
if ($deployment->getAttribute('activate') === true) {
$resource->setAttribute('live', 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) {
switch ($resource->getCollection()) {
case 'functions':
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getSequence());
$resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
$resource = $dbForProject->updateDocument('functions', $resource->getId(), new Document([
'live' => true,
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentCreatedAt' => $deployment->getCreatedAt(),
]));
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
@@ -1098,19 +1118,21 @@ class Builds extends Action
$rulesUpdated = false;
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment, &$rulesUpdated) {
$rulesUpdated = true;
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
break;
case 'sites':
$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);
$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(),
]));
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('type', ['deployment']),
@@ -1121,10 +1143,10 @@ class Builds extends Action
];
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
break;
@@ -1166,11 +1188,10 @@ class Builds extends Action
'region' => $project->getAttribute('region')
]));
} catch (Duplicate $err) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $ruleId, new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}
$queries = [
@@ -1183,10 +1204,10 @@ class Builds extends Action
];
$dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
}
}
@@ -1201,7 +1222,7 @@ class Builds extends Action
->trigger();
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -1224,7 +1245,7 @@ class Builds extends Action
Console::error($th->getTraceAsString());
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -1256,8 +1277,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -1283,6 +1303,8 @@ 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
@@ -1511,4 +1533,22 @@ 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();
}
}
@@ -63,8 +63,8 @@ class XList extends Base
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
$maxCpus = System::getEnv('_APP_COMPUTE_CPUS', 0);
$maxMemory = System::getEnv('_APP_COMPUTE_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
// Treat 0 as no limit
@@ -37,6 +37,7 @@ class Action extends UtopiaAction
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
return [
'bucket' => $bucket,
'file' => $file,
+4 -1
View File
@@ -1117,6 +1117,9 @@ class Deletes extends Action
): void {
$start = \microtime(true);
$deleteBatchSize = Database::DELETE_BATCH_SIZE;
$deleteBatchSize = 500; // TODO: Set right value in DB library after investigation
/**
* deleteDocuments uses a cursor, we need to add a unique order by field or use default
*/
@@ -1124,7 +1127,7 @@ class Deletes extends Action
$count = $database->deleteDocuments(
$collection,
$queries,
Database::DELETE_BATCH_SIZE,
$deleteBatchSize,
$callback
);
} catch (Throwable $th) {
@@ -360,6 +360,7 @@ class OpenAPI3 extends Format
break;
case 'Utopia\Validator\Host':
case 'Utopia\Validator\URL':
case 'Appwrite\Network\Validator\Redirect':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
@@ -385,6 +385,7 @@ class Swagger2 extends Format
break;
case 'Utopia\Validator\Host':
case 'Utopia\Validator\URL':
case 'Appwrite\Network\Validator\Redirect':
$node['type'] = $validator->getType();
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
+16 -3
View File
@@ -26,7 +26,20 @@ class URL
'fragment' => '',
];
return \array_merge($default, \parse_url($url));
$parsed = \parse_url($url);
if (is_array($parsed)) {
return \array_merge($default, $parsed);
}
// see if $url is just a scheme
if (preg_match('/^([a-z][a-z0-9+.-]*):/i', $url, $matches)) {
$scheme = $matches[1];
return \array_merge($default, [
'scheme' => $scheme
]);
}
throw new \InvalidArgumentException('Invalid URL: ' . $url);
}
/**
@@ -55,9 +68,9 @@ class URL
$parts['user'] = isset($url['user']) ? $url['user'] : '';
$parts['pass'] = isset($url['pass']) ? ':' . $url['pass'] : '';
$parts['pass'] = !empty($url['pass']) ? ':' . $url['pass'] : '';
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'] . '@' : '';
$parts['pass'] = ($parts['user'] || !empty($parts['pass'])) ? $parts['pass'] . '@' : '';
$parts['path'] = isset($url['path']) ? $url['path'] : '';
+16 -1
View File
@@ -2,13 +2,28 @@
namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
class V20 extends Filter
{
// Convert 1.8 format to 1.7 format
// removing $sequence from all versions less than 1.8
public function parse(array $content, string $model): array
{
$parsedResponse = $content;
$parsedResponse = match($model) {
Response::MODEL_DOCUMENT => $this->parseDocument($content),
Response::MODEL_DOCUMENT_LIST => $this->handleList($content, 'documents', fn ($item) => $this->parseDocument($item)),
default => $parsedResponse,
};
return $parsedResponse;
}
protected function parseDocument(array $content): array
{
unset($content['$sequence']);
return $content;
}
}
+3 -3
View File
@@ -925,7 +925,8 @@ class UsageTest extends Scope
[
'functionId' => 'unique()',
'name' => 'Test',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
@@ -947,8 +948,7 @@ class UsageTest extends Scope
$this->assertNotEmpty($response['body']['$id']);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true,
]);
$this->assertNotEmpty($deploymentId);
@@ -1995,6 +1995,36 @@ trait DatabasesBase
],
]);
$this->assertEquals(2, $documents['body']['total']);
// test without passing permissions
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Ragnarok',
'releaseYear' => 2000
]
]);
$this->assertEquals(200, $document['headers']['status-code']);
$this->assertEquals('Thor: Ragnarok', $document['body']['title']);
$document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $document['headers']['status-code']);
$deleteResponse = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(204, $deleteResponse['headers']['status-code']);
}
/**
@@ -21,8 +21,8 @@ class FunctionsConsoleClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -39,8 +39,8 @@ class FunctionsConsoleClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test Failure',
'execute' => ['some-random-string'],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
]);
$this->assertEquals(400, $function2['headers']['status-code']);
@@ -453,7 +453,7 @@ class FunctionsConsoleClientTest extends Scope
{
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Variable E2E Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -481,7 +481,7 @@ class FunctionsConsoleClientTest extends Scope
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -502,7 +502,7 @@ class FunctionsConsoleClientTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Download Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -511,7 +511,7 @@ class FunctionsConsoleClientTest extends Scope
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -43,8 +43,8 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -52,8 +52,7 @@ class FunctionsCustomClientTest extends Scope
'timeout' => 10,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -84,13 +83,12 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -107,8 +105,8 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('Test', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('22', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
$this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
@@ -142,10 +140,10 @@ class FunctionsCustomClientTest extends Scope
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test guest execution',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
@@ -154,8 +152,7 @@ class FunctionsCustomClientTest extends Scope
'timeout' => 10,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -175,8 +172,8 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -193,15 +190,14 @@ class FunctionsCustomClientTest extends Scope
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test synchronous execution',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -214,11 +210,11 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
$this->assertEquals('Test', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals('Test synchronous execution', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('22', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
$this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
@@ -239,12 +235,12 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'entrypoint' => 'index.js'
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -340,7 +336,7 @@ class FunctionsCustomClientTest extends Scope
'limit' => 5,
'offset' => 2,
'useCases' => ['databases'],
'runtimes' => ['node-16.0']
'runtimes' => ['node-22']
]);
$this->assertEquals(200, $templates['headers']['status-code']);
@@ -352,7 +348,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertContains($template['useCases'][0], ['databases']);
}
$this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
$this->assertContains('node-22', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
/**
* Test for FAILURE
@@ -35,7 +35,7 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'specification' => $specifications['body']['specifications'][0]['slug']
]);
$this->assertEquals(201, $function['headers']['status-code']);
@@ -50,7 +50,7 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'specification' => 'cheap-please'
]);
$this->assertEquals(400, $function['headers']['status-code']);
@@ -64,8 +64,8 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'buckets.*.create',
'buckets.*.delete',
@@ -79,7 +79,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$this->assertEquals('Test', $function['body']['name']);
$this->assertEquals('php-8.0', $function['body']['runtime']);
$this->assertEquals('node-22', $function['body']['runtime']);
$this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt']));
$this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt']));
$this->assertEquals('', $function['body']['deploymentId']);
@@ -180,7 +180,7 @@ class FunctionsCustomServerTest extends Scope
// Test search runtime
$functions = $this->listFunctions([
'search' => 'php-8.0'
'search' => 'node-22'
]);
$this->assertEquals($functions['headers']['status-code'], 200);
@@ -193,8 +193,8 @@ class FunctionsCustomServerTest extends Scope
$this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test 2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'buckets.*.create',
'buckets.*.delete',
@@ -286,8 +286,8 @@ class FunctionsCustomServerTest extends Scope
],
'schedule' => '0 0 1 1 *',
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
]);
$dateValidator = new DatetimeValidator();
@@ -322,8 +322,8 @@ class FunctionsCustomServerTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -338,8 +338,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
'x-sdk-language' => 'cli',
], [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -363,7 +362,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $starterTemplate['headers']['status-code']);
$phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
return $runtime['name'] === 'php-8.0';
return $runtime['name'] === 'node-22';
}))[0];
// If this fails, the template has variables, and this test needs to be updated
@@ -373,7 +372,7 @@ class FunctionsCustomServerTest extends Scope
[
'functionId' => ID::unique(),
'name' => $starterTemplate['body']['name'],
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'execute' => $starterTemplate['body']['permissions'],
'entrypoint' => $phpRuntime['entrypoint'],
'events' => $starterTemplate['body']['events'],
@@ -514,7 +513,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $data['functionId'];
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -522,7 +521,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEquals('waiting', $deployment['body']['status']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
$this->assertEquals('index.js', $deployment['body']['entrypoint']);
$deploymentIdActive = $deployment['body']['$id'] ?? '';
@@ -533,7 +532,7 @@ class FunctionsCustomServerTest extends Scope
}, 50000, 500);
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => 'false'
]);
@@ -573,7 +572,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $data['functionId'];
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => 'false'
]);
@@ -582,7 +581,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
$this->assertEquals('index.js', $deployment['body']['entrypoint']);
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
@@ -595,12 +594,12 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
/**
* Build worker still runs the build.
* 30s sleep gives worker enough time to finish build.
* After build finished, it should still be canceled, not ready.
*/
\sleep(30);
// Ensures worker got eventually aware of cancellation and reacted properly
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsString('Build has been canceled.', $deployment['body']['buildLogs']);
});
$deployment = $this->getDeployment($functionId, $deploymentId);
@@ -618,7 +617,7 @@ class FunctionsCustomServerTest extends Scope
*/
$functionId = $data['functionId'];
$folder = 'php-large';
$folder = 'large';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
@@ -633,13 +632,13 @@ class FunctionsCustomServerTest extends Scope
];
$id = '';
while (!feof($handle)) {
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'php-large-fx.tar.gz');
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'large-fx.tar.gz');
$headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size - 1) . '/' . $size;
if (!empty($id)) {
$headers['x-appwrite-id'] = $id;
}
$largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge($headers, $this->getHeaders()), [
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
'code' => $curlFile,
'activate' => true,
'commands' => 'cp blue.mp4 copy.mp4 && ls -al' // +7MB buildSize
@@ -652,7 +651,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $largeTag['headers']['status-code']);
$this->assertNotEmpty($largeTag['body']['$id']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($largeTag['body']['$createdAt']));
$this->assertEquals('index.php', $largeTag['body']['entrypoint']);
$this->assertEquals('index.js', $largeTag['body']['entrypoint']);
$this->assertGreaterThan(1024 * 1024 * 5, $largeTag['body']['sourceSize']); // ~7MB video file
$this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['sourceSize']); // ~7MB video file
@@ -903,8 +902,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString($data['deploymentId'], $execution['body']['responseBody']);
$this->assertStringContainsString('Test1', $execution['body']['responseBody']);
$this->assertStringContainsString('http', $execution['body']['responseBody']);
$this->assertStringContainsString('PHP', $execution['body']['responseBody']);
$this->assertStringContainsString('8.0', $execution['body']['responseBody']);
$this->assertStringContainsString('Node.js', $execution['body']['responseBody']);
$this->assertStringContainsString('22', $execution['body']['responseBody']);
$this->assertStringContainsString('Global Variable Value', $execution['body']['responseBody']);
// $this->assertStringContainsString('êä', $execution['body']['responseBody']); // tests unknown utf-8 chars
$this->assertNotEmpty($execution['body']['errors']);
@@ -1017,8 +1016,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertStringContainsString('Test1', $execution['body']['responseBody']);
$this->assertStringContainsString('http', $execution['body']['responseBody']);
$this->assertStringContainsString('PHP', $execution['body']['responseBody']);
$this->assertStringContainsString('8.0', $execution['body']['responseBody']);
$this->assertStringContainsString('Node.js', $execution['body']['responseBody']);
$this->assertStringContainsString('22', $execution['body']['responseBody']);
// $this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertLessThan(1.500, $execution['body']['duration']);
@@ -1120,8 +1119,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => Specification::S_1VCPU_1GB,
]);
@@ -1148,8 +1147,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => Specification::S_1VCPU_512MB,
]);
@@ -1178,8 +1177,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => 's-2vcpu-512mb', // Invalid specification
]);
@@ -1236,9 +1235,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test php-8.0',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test timeout execution',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [],
'schedule' => '',
'timeout' => 5, // Should timeout after 5 seconds
@@ -1280,12 +1279,12 @@ class FunctionsCustomServerTest extends Scope
*/
public function provideCustomExecutions(): array
{
// Most disabled to keep tests fast
return [
['folder' => 'php-fn', 'name' => 'php-8.0', 'entrypoint' => 'index.php', 'runtimeName' => 'PHP', 'runtimeVersion' => '8.0'],
['folder' => 'node', 'name' => 'node-18.0', 'entrypoint' => 'index.js', 'runtimeName' => 'Node.js', 'runtimeVersion' => '18.0'],
['folder' => 'python', 'name' => 'python-3.9', 'entrypoint' => 'main.py', 'runtimeName' => 'Python', 'runtimeVersion' => '3.9'],
['folder' => 'ruby', 'name' => 'ruby-3.1', 'entrypoint' => 'main.rb', 'runtimeName' => 'Ruby', 'runtimeVersion' => '3.1'],
// Swift and Dart disabled on purpose, as it's very slow.
// ['folder' => 'php-fn', 'name' => 'php-8.0', 'entrypoint' => 'index.php', 'runtimeName' => 'PHP', 'runtimeVersion' => '8.0'],
['folder' => 'node', 'name' => 'node-22', 'entrypoint' => 'index.js', 'runtimeName' => 'Node.js', 'runtimeVersion' => '22'],
// ['folder' => 'python', 'name' => 'python-3.9', 'entrypoint' => 'main.py', 'runtimeName' => 'Python', 'runtimeVersion' => '3.9'],
// ['folder' => 'ruby', 'name' => 'ruby-3.1', 'entrypoint' => 'main.rb', 'runtimeName' => 'Ruby', 'runtimeVersion' => '3.1'],
// [ 'folder' => 'dart', 'name' => 'dart-2.15', 'entrypoint' => 'main.dart', 'runtimeName' => 'Dart', 'runtimeVersion' => '2.15' ],
// [ 'folder' => 'swift', 'name' => 'swift-5.5', 'entrypoint' => 'index.swift', 'runtimeName' => 'Swift', 'runtimeVersion' => '5.5' ],
];
@@ -1324,27 +1323,14 @@ class FunctionsCustomServerTest extends Scope
]);
$execution = $this->createExecution($functionId, [
'body' => 'foobar',
'async' => 'false'
]);
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
$this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals($runtimeName, $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals($runtimeVersion, $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals('variable', $output['CUSTOM_VARIABLE']);
$this->assertEmpty($output['APPWRITE_FUNCTION_USER_ID']);
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
$this->assertStringContainsString('Amazing Function Log', $execution['body']['logs']);
$this->assertEquals('OK', $execution['body']['responseBody']);
$this->assertEmpty($execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1357,7 +1343,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('Amazing Function Log', $executions['body']['executions'][0]['logs']);
$this->assertEquals(200, $executions['body']['executions'][0]['responseStatusCode']);
$this->assertEmpty($executions['body']['executions'][0]['responseBody']);
$this->cleanupFunction($functionId);
}
@@ -1366,15 +1353,14 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-response'),
'code' => $this->packageFunction('binary-response'),
'activate' => true
]);
@@ -1415,15 +1401,14 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-request'),
'code' => $this->packageFunction('binary-request'),
'activate' => true
]);
@@ -1461,49 +1446,6 @@ class FunctionsCustomServerTest extends Scope
$this->cleanupFunction($functionId);
}
public function testv2Function()
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP V2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [],
'timeout' => 15,
]);
$variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', [
'content-type' => 'application/json',
'origin' => 'http://localhost',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-mode' => 'admin',
], [
'functionId' => $functionId
]);
$this->assertEquals(204, $variable['headers']['status-code']);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-v2'),
'activate' => true
]);
$execution = $this->createExecution($functionId, [
'body' => 'foobar',
'async' => 'false'
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(true, $output['v2Woks']);
$this->cleanupFunction($functionId);
}
public function testGetRuntimes()
{
$runtimes = $this->client->call(Client::METHOD_GET, '/functions/runtimes', array_merge([
@@ -1531,17 +1473,16 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Event executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Event executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
],
'timeout' => 15,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-event'),
'code' => $this->packageFunction('event-handler'),
'activate' => true
]);
@@ -1584,17 +1525,16 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Scopes executions',
'commands' => 'bash setup.sh && composer install',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Scopes executions',
'commands' => 'bash setup.sh && npm install',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'scopes' => ['users.read'],
'timeout' => 15,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-scopes'),
'code' => $this->packageFunction('dynamic-api-key'),
'activate' => true,
]);
@@ -1643,16 +1583,15 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Cookie executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
]);
$this->assertNotEmpty($functionId);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -1685,9 +1624,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Cookie executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1695,8 +1634,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
@@ -1743,9 +1681,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1753,8 +1691,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-response'),
'code' => $this->packageFunction('binary-response'),
'activate' => true
]);
@@ -1778,9 +1715,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1788,8 +1725,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-request'),
'code' => $this->packageFunction('binary-request'),
'activate' => true
]);
@@ -1816,8 +1752,8 @@ class FunctionsCustomServerTest extends Scope
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
]);
@@ -1851,8 +1787,8 @@ class FunctionsCustomServerTest extends Scope
$function1Id = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1860,8 +1796,8 @@ class FunctionsCustomServerTest extends Scope
$function2Id = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1892,7 +1828,7 @@ class FunctionsCustomServerTest extends Scope
{
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Logging Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -1908,7 +1844,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -1985,7 +1921,7 @@ class FunctionsCustomServerTest extends Scope
// Check if the function specifications are correctly set in builds
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Specification Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -2001,7 +1937,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $functionId = $function['body']['$id'] ?? '';
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2027,7 +1963,7 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Duplicate Deployment Test',
'entrypoint' => 'index.js',
'commands' => ''
@@ -2035,7 +1971,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertNotEmpty($functionId);
$deploymentId1 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId1);
@@ -2045,7 +1981,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['responseBody']);
$function = $this->updateFunction($functionId, [
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Duplicate Deployment Test',
'entrypoint' => 'index.js',
'commands' => 'rm index.js && mv maintenance.js index.js'
@@ -2089,13 +2025,12 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'name' => 'Re-activate Test',
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
]);
$this->assertNotEmpty($functionId);
$function = $this->getFunction($functionId);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertArrayHasKey('latestDeploymentId', $function['body']);
@@ -2106,7 +2041,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEmpty($function['body']['latestDeploymentStatus']);
$deploymentId1 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId1);
@@ -2124,7 +2059,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('cookieValue', $execution['body']['responseBody']);
$deploymentId2 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId2);
@@ -2192,8 +2127,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => 'cd non-existing-directory',
'execute' => ['any']
@@ -2203,8 +2138,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deployment = $this->createDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2221,8 +2155,7 @@ class FunctionsCustomServerTest extends Scope
// canceled deployment
$deployment = $this->createDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2250,8 +2183,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => '',
'execute' => ['users']
@@ -2262,7 +2195,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -2284,8 +2217,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => '',
'execute' => ['any']
@@ -2296,7 +2229,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -25,8 +25,8 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -36,8 +36,7 @@ class FunctionsScheduleTest extends Scope
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -77,14 +76,13 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -179,15 +177,14 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -26,8 +26,8 @@ class FunctionsClientTest extends Scope
'variables' => [
'functionId' => ID::unique(),
'name' => 'Test Function',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'execute' => [Role::any()->toString()],
]
];
@@ -96,7 +96,7 @@ class FunctionsClientTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => $this->packageFunction('php')
'code' => $this->packageFunction('basic')
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', [
@@ -26,8 +26,8 @@ class FunctionsServerTest extends Scope
'variables' => [
'functionId' => ID::unique(),
'name' => 'Test Function',
'entrypoint' => 'index.php',
'runtime' => 'php-8.0',
'entrypoint' => 'index.js',
'runtime' => 'node-22',
'execute' => [Role::any()->toString()],
]
];
@@ -95,7 +95,7 @@ class FunctionsServerTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
@@ -805,13 +805,12 @@ trait MigrationsBase
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php'
'runtime' => 'node-22',
'entrypoint' => 'index.js'
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -855,8 +854,8 @@ trait MigrationsBase
$this->assertEquals($functionId, $response['body']['$id']);
$this->assertEquals('Test', $response['body']['name']);
$this->assertEquals('php-8.0', $response['body']['runtime']);
$this->assertEquals('index.php', $response['body']['entrypoint']);
$this->assertEquals('node-22', $response['body']['runtime']);
$this->assertEquals('index.js', $response['body']['entrypoint']);
$this->assertEventually(function () use ($functionId) {
@@ -30,8 +30,6 @@ class ProjectsCustomServerTest extends Scope
'domain' => $testId . '-api.appwrite.test',
]);
\var_dump($response);
$this->assertEquals(201, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
+2 -2
View File
@@ -227,7 +227,7 @@ trait ProxyBase
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Proxy Function',
'entrypoint' => 'index.js',
'commands' => '',
@@ -244,7 +244,7 @@ trait ProxyBase
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => 'true'
]);
@@ -874,8 +874,8 @@ class RealtimeConsoleClientTest extends Scope
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -914,8 +914,7 @@ class RealtimeConsoleClientTest extends Scope
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -1296,10 +1296,10 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test timeout execution',
'execute' => ['users'],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -1313,7 +1313,6 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('timeout'),
'activate' => true
]);
@@ -14,6 +14,9 @@ class SitesConsoleClientTest extends Scope
use SideConsole;
use SitesBase;
/**
* @group screenshots
*/
public function testSiteScreenshot(): void
{
$siteId = $this->setupSite([
@@ -937,12 +937,12 @@ class SitesCustomServerTest extends Scope
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
/**
* Build worker still runs the build.
* 30s sleep gives worker enough time to finish build.
* After build finished, it should still be canceled, not ready.
*/
\sleep(30);
// Ensures worker got eventually aware of cancellation and reacted properly
$this->assertEventually(function () use ($siteId, $deploymentId) {
$deployment = $this->getDeployment($siteId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsString('Build has been canceled.', $deployment['body']['buildLogs']);
});
$deployment = $this->getDeployment($siteId, $deploymentId);
+86
View File
@@ -5,6 +5,8 @@ namespace Tests\E2E\Services\Tokens;
use CURLFile;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
trait TokensBase
{
@@ -275,4 +277,88 @@ trait TokensBase
$this->assertEquals($image->getImageHeight(), $original->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
}
public function testFileAccessWithFileSecurity(): void
{
$bucket = $this->client->call(
Client::METHOD_POST,
'/storage/buckets',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'name' => 'Test Bucket',
'bucketId' => ID::unique(),
'fileSecurity' => true,
'allowedFileExtensions' => ['jpg', 'png', 'jfif'],
]
);
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertNotEmpty($bucket['body']['$id']);
$bucketId = $bucket['body']['$id'];
$file = $this->client->call(
Client::METHOD_POST,
'/storage/buckets/' . $bucketId . '/files',
[
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'fileId' => ID::unique(),
'permissions' => [ Permission::read(Role::label('devrel')) ],
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
]
);
$fileId = $file['body']['$id'];
$token = $this->client->call(
Client::METHOD_POST,
'/tokens/buckets/' . $bucketId . '/files/' . $fileId,
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]
);
$jwtToken = $token['body']['secret'];
$endpoints = ['preview', 'view', 'download'];
foreach ($endpoints as $endpoint) {
$response = $this->client->call(
Client::METHOD_GET,
"/storage/buckets/$bucketId/files/$fileId/$endpoint",
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
],
[
'token' => $jwtToken
]
);
$this->assertNotEmpty($response['body']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('image/png', $response['headers']['content-type']);
if ($endpoint === 'download') {
$image = new \Imagick();
$image->readImageBlob($response['body']);
$original = new \Imagick(__DIR__ . '/../../../resources/logo.png');
$this->assertEquals($original->getImageWidth(), $image->getImageWidth());
$this->assertEquals($original->getImageHeight(), $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
}
}
}
}
@@ -586,8 +586,8 @@ class WebhooksCustomServerTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -626,8 +626,8 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'execute' => [Role::any()->toString()],
'vars' => [
'key1' => 'value1',
@@ -683,7 +683,7 @@ class WebhooksCustomServerTest extends Scope
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'activate' => true
]);
+46
View File
@@ -0,0 +1,46 @@
module.exports = async(context) => {
context.log('log-works');
context.error('error-log-works');
if(context.req.headers['x-appwrite-user-jwt']) {
context.log('jwt-is-valid');
} else {
context.log('jwt-is-invalid');
}
if(context.req.path === '/custom-response') {
const code = +(context.req.query['code'] || '200');
const body = context.req.query['body'] || '';
return context.res.send(body, code);
}
context.log('body-is-' + (context.req.body ?? ''));
context.log('custom-header-is-' + (context.req.headers['x-custom-header'] ?? ''));
context.log('method-is-' + (context.req.method ?? '').toLowerCase());
context.log('path-is-' + (context.req.path ?? ''));
context.log('user-is-' + (context.req.headers['x-appwrite-user-id'] ?? ''));
const statusCode = context.req.query['code'] || '200';
return context.res.json({
'APPWRITE_FUNCTION_ID' : process.env.APPWRITE_FUNCTION_ID ?? '',
'APPWRITE_FUNCTION_NAME' : process.env.APPWRITE_FUNCTION_NAME ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : process.env.APPWRITE_FUNCTION_DEPLOYMENT ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : process.env.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : process.env.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_VERSION' : process.env.APPWRITE_VERSION ?? '',
'APPWRITE_REGION' : process.env.APPWRITE_REGION ?? '',
'UNICODE_TEST' : "êä",
'GLOBAL_VARIABLE' : process.env.GLOBAL_VARIABLE ?? '',
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : process.env.APPWRITE_FUNCTION_PROJECT_ID,
'APPWRITE_FUNCTION_MEMORY' : process.env.APPWRITE_FUNCTION_MEMORY,
'APPWRITE_FUNCTION_CPUS' : process.env.APPWRITE_FUNCTION_CPUS,
'CUSTOM_VARIABLE' : process.env.CUSTOM_VARIABLE
}, +statusCode);
}
@@ -0,0 +1,6 @@
const crypto = require('crypto')
module.exports = async(context) => {
const hash = crypto.createHash('md5').update(context.req.bodyBinary).digest("hex")
return context.res.send(hash);
};
@@ -0,0 +1,4 @@
module.exports = async(context) => {
const bytes = Buffer.from(Uint8Array.from([0, 10, 255]));
return context.res.binary(bytes);
};
@@ -0,0 +1,3 @@
module.exports = async(context) => {
return context.res.send(context.req.headers['cookie'] ?? '');
};
-21
View File
@@ -1,21 +0,0 @@
import 'dart:io' show Platform;
Future<dynamic> main(final context) async {
context.log('Amazing Function Log');
response.json({
'APPWRITE_FUNCTION_ID' : Platform.environment['APPWRITE_FUNCTION_ID'] ?? '',
'APPWRITE_FUNCTION_NAME' : Platform.environment['APPWRITE_FUNCTION_NAME'] ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : Platform.environment['APPWRITE_FUNCTION_DEPLOYMENT'] ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : Platform.environment['APPWRITE_FUNCTION_RUNTIME_NAME'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' : Platform.environment['APPWRITE_FUNCTION_RUNTIME_VERSION'] ?? '',
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'] ?? '',
'CUSTOM_VARIABLE' : request.variables['CUSTOM_VARIABLE']
});
}
@@ -0,0 +1,15 @@
const sdk = require('node-appwrite');
module.exports = async(context) => {
const client = new sdk.Client();
client.setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT);
client.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID);
client.setKey(context.req.headers['x-appwrite-key']);
const users = new sdk.Users(client);
const response = await users.list();
context.log(JSON.stringify(response));
return context.res.json(response);
};
@@ -0,0 +1,31 @@
{
"name": "dynamic-api-key",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dynamic-api-key",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"node-appwrite": "^17.0.0"
}
},
"node_modules/node-appwrite": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-17.0.0.tgz",
"integrity": "sha512-5Moi5ENPnoAfU1/6CZP9K2NTuB6Nm3dSyhokno+24RDuP7czjXCdwzfeyjmyHieggbrLkN89AYSOv9W1XkCL9w==",
"license": "BSD-3-Clause",
"dependencies": {
"node-fetch-native-with-agent": "1.7.2"
}
},
"node_modules/node-fetch-native-with-agent": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz",
"integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==",
"license": "MIT"
}
}
}
@@ -0,0 +1,15 @@
{
"name": "dynamic-api-key",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"node-appwrite": "^17.0.0"
}
}
@@ -1,6 +1,7 @@
ENDPOINT="$APPWRITE_FUNCTION_API_ENDPOINT/users"
PROJECT_ID="$APPWRITE_FUNCTION_PROJECT_ID"
API_KEY="$APPWRITE_FUNCTION_API_KEY"
apk add curl
curl -v -X GET $ENDPOINT -H "x-appwrite-project: $PROJECT_ID" -H "x-appwrite-key: $API_KEY"
@@ -0,0 +1,5 @@
module.exports = async(context) => {
context.log(context.req.body.$id);
context.log(context.req.body.name);
return context.res.empty();
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = async(context) => {
return context.res.empty();
};
+1 -19
View File
@@ -1,21 +1,3 @@
module.exports = async(context) => {
context.log('Amazing Function Log');
return context.res.json({
'APPWRITE_FUNCTION_ID' : process.env.APPWRITE_FUNCTION_ID ?? '',
'APPWRITE_FUNCTION_NAME' : process.env.APPWRITE_FUNCTION_NAME ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : process.env.APPWRITE_FUNCTION_DEPLOYMENT ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : process.env.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : process.env.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : process.env.APPWRITE_FUNCTION_PROJECT_ID,
'APPWRITE_FUNCTION_MEMORY' : process.env.APPWRITE_FUNCTION_MEMORY,
'APPWRITE_FUNCTION_CPUS' : process.env.APPWRITE_FUNCTION_CPUS,
'CUSTOM_VARIABLE' : process.env.CUSTOM_VARIABLE
});
return context.res.send('OK');
}
@@ -1,6 +0,0 @@
<?php
return function ($context) {
$hash = md5($context->req->bodyBinary);
return $context->res->send($hash);
};
@@ -1,6 +0,0 @@
<?php
return function ($context) {
$bytes = pack('C*', ...[0, 10, 255]);
return $context->res->binary($bytes);
};
@@ -1,5 +0,0 @@
<?php
return function ($context) {
return $context->res->send($context->req->headers['cookie'] ?? '');
};
@@ -1,8 +0,0 @@
<?php
return function ($context) {
$context->log($context->req->body['$id']);
$context->log($context->req->body['name']);
return $context->res->empty();
};
@@ -1,23 +0,0 @@
<?php
return function ($context) {
$context->log('Amazing Function Log');
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_VERSION' => \getenv('APPWRITE_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'APPWRITE_FUNCTION_EVENT' => $context->req->headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' => $context->req->bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' => $context->req->bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' => $context->req->headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $context->req->headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' => \getenv('APPWRITE_FUNCTION_PROJECT_ID') ?: '',
'CUSTOM_VARIABLE' => \getenv('CUSTOM_VARIABLE') ?: '',
]);
};
@@ -1,18 +0,0 @@
{
"name": "appwrite/cloud-function-demo",
"description": "Demo cloud function script",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Team Appwrite",
"email": "team@appwrite.io"
}
],
"require": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*",
"appwrite/appwrite": "1.1.*"
}
}
-64
View File
@@ -1,64 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "afdff6a172e6c44aee11f1562175f81a",
"packages": [
{
"name": "appwrite/appwrite",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "98b327d3fd18a72f4582019916afd735a0e9e0e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/98b327d3fd18a72f4582019916afd735a0e9e0e7",
"reference": "98b327d3fd18a72f4582019916afd735a0e9e0e7",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": ">=7.1.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.35"
},
"type": "library",
"autoload": {
"psr-4": {
"Appwrite\\": "src/Appwrite"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks",
"support": {
"email": "team@localhost.test",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
"source": "https://github.com/appwrite/sdk-for-php/tree/1.1.2",
"url": "https://appwrite.io/support"
},
"time": "2020-08-15T18:24:32+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}
@@ -1,16 +0,0 @@
<?php
return function ($context) {
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'APPWRITE_FUNCTION_CPUS' => \getenv('APPWRITE_FUNCTION_CPUS') ?: '',
'APPWRITE_FUNCTION_MEMORY' => \getenv('APPWRITE_FUNCTION_MEMORY') ?: '',
'UNICODE_TEST' => "êä"
]);
};
@@ -1,18 +0,0 @@
{
"name": "appwrite/php-scopes",
"description": "PHP scopes test script",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Team Appwrite",
"email": "team@appwrite.io"
}
],
"require": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*",
"appwrite/appwrite": "11.0.*"
}
}
@@ -1,18 +0,0 @@
<?php
require 'vendor/autoload.php';
use Appwrite\Client;
use Appwrite\Services\Users;
return function ($context) {
$client = new Client();
$client
->setEndpoint(getenv('APPWRITE_FUNCTION_API_ENDPOINT'))
->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))
->setKey($context->req->headers['x-appwrite-key']);
$users = new Users($client);
$response = $users->list();
$context->log($response);
return $context->res->json($response);
};
@@ -1,7 +0,0 @@
<?php
return function ($req, $res) {
$res->json([
'v2Woks' => true
]);
};
-39
View File
@@ -1,39 +0,0 @@
<?php
return function ($context) {
if ($context->req->path === '/custom-response') {
$code = (int) ($context->req->query['code'] ?? '200');
$body = $context->req->query['body'] ?? '';
return $context->res->send($body, $code);
}
$context->log('body-is-' . ($context->req->body ?? ''));
$context->log('custom-header-is-' . ($context->req->headers['x-custom-header'] ?? ''));
$context->log('method-is-' . \strtolower($context->req->method ?? ''));
$context->log('path-is-' . ($context->req->path ?? ''));
$context->log('user-is-' . $context->req->headers['x-appwrite-user-id'] ?? '');
if (empty($context->req->headers['x-appwrite-user-jwt'] ?? '')) {
$context->log('jwt-is-invalid');
} else {
$context->log('jwt-is-valid');
}
$context->error('error-log-works');
$statusCode = $context->req->query['code'] ?? '200';
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'UNICODE_TEST' => "êä",
'GLOBAL_VARIABLE' => \getenv('GLOBAL_VARIABLE') ?: '',
'APPWRITE_FUNCTION_CPUS' => \getenv('APPWRITE_FUNCTION_CPUS') ?: '',
'APPWRITE_FUNCTION_MEMORY' => \getenv('APPWRITE_FUNCTION_MEMORY') ?: '',
], \intval($statusCode));
};
-21
View File
@@ -1,21 +0,0 @@
import json
import os
def main(context):
context.log('Amazing Function Log')
return context.res.json({
'APPWRITE_FUNCTION_ID' : os.environ.get('APPWRITE_FUNCTION_ID',''),
'APPWRITE_FUNCTION_NAME' : os.environ.get('APPWRITE_FUNCTION_NAME',''),
'APPWRITE_FUNCTION_DEPLOYMENT' : os.environ.get('APPWRITE_FUNCTION_DEPLOYMENT',''),
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers.get('x-appwrite-trigger', ''),
'APPWRITE_FUNCTION_RUNTIME_NAME' : os.environ.get('APPWRITE_FUNCTION_RUNTIME_NAME',''),
'APPWRITE_FUNCTION_RUNTIME_VERSION' : os.environ.get('APPWRITE_FUNCTION_RUNTIME_VERSION',''),
'APPWRITE_FUNCTION_EVENT' : context.req.headers.get('x-appwrite-event', ''),
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.body_raw,
'APPWRITE_FUNCTION_DATA' : context.req.body_raw,
'APPWRITE_FUNCTION_USER_ID' : context.req.headers.get('x-appwrite-user-id', ''),
'APPWRITE_FUNCTION_JWT' : context.req.headers.get('x-appwrite-user-jwt', ''),
'APPWRITE_FUNCTION_PROJECT_ID' : os.environ.get('APPWRITE_FUNCTION_PROJECT_ID',''),
'CUSTOM_VARIABLE' : os.environ.get('CUSTOM_VARIABLE',''),
})
-19
View File
@@ -1,19 +0,0 @@
def main(context)
context.log('Amazing Function Log')
return context.res.json({
'APPWRITE_FUNCTION_ID' => ENV['APPWRITE_FUNCTION_ID'] || '',
'APPWRITE_FUNCTION_NAME' => ENV['APPWRITE_FUNCTION_NAME'] || '',
'APPWRITE_FUNCTION_DEPLOYMENT' => ENV['APPWRITE_FUNCTION_DEPLOYMENT'] || '',
'APPWRITE_FUNCTION_TRIGGER' => context.req.headers['x-appwrite-trigger'] || '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => ENV['APPWRITE_FUNCTION_RUNTIME_NAME'] || '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => ENV['APPWRITE_FUNCTION_RUNTIME_VERSION'] || '',
'APPWRITE_FUNCTION_EVENT' => context.req.headers['x-appwrite-event'] || '',
'APPWRITE_FUNCTION_EVENT_DATA' => context.req.body_raw || '',
'APPWRITE_FUNCTION_DATA' => context.req.body_raw || '',
'APPWRITE_FUNCTION_USER_ID' => context.req.headers['x-appwrite-user-id'] || '',
'APPWRITE_FUNCTION_JWT' => context.req.headers['x-appwrite-user-jwt'] || '',
'APPWRITE_FUNCTION_PROJECT_ID' => ENV['APPWRITE_FUNCTION_PROJECT_ID'] || '',
'CUSTOM_VARIABLE' => ENV['CUSTOM_VARIABLE'] || ''
})
end
@@ -1,17 +0,0 @@
func main(req: RequestValue, res: RequestResponse) throws -> RequestResponse {
return res.json(data: [
"APPWRITE_FUNCTION_ID": req.variables["APPWRITE_FUNCTION_ID"],
"APPWRITE_FUNCTION_NAME": req.variables["APPWRITE_FUNCTION_NAME"],
"APPWRITE_FUNCTION_DEPLOYMENT": req.variables["APPWRITE_FUNCTION_DEPLOYMENT"],
"APPWRITE_FUNCTION_TRIGGER": req.variables["APPWRITE_FUNCTION_TRIGGER"],
"APPWRITE_FUNCTION_RUNTIME_NAME": req.variables["APPWRITE_FUNCTION_RUNTIME_NAME"],
"APPWRITE_FUNCTION_RUNTIME_VERSION": req.variables["APPWRITE_FUNCTION_RUNTIME_VERSION"],
"APPWRITE_FUNCTION_EVENT": req.variables["APPWRITE_FUNCTION_EVENT"],
"APPWRITE_FUNCTION_EVENT_DATA": req.variables["APPWRITE_FUNCTION_EVENT_DATA"],
"APPWRITE_FUNCTION_DATA": req.variables["APPWRITE_FUNCTION_DATA"],
"APPWRITE_FUNCTION_USER_ID": req.variables["APPWRITE_FUNCTION_USER_ID"],
"APPWRITE_FUNCTION_JWT": req.variables["APPWRITE_FUNCTION_JWT"],
"APPWRITE_FUNCTION_PROJECT_ID": req.variables["APPWRITE_FUNCTION_PROJECT_ID"],
"CUSTOM_VARIABLE": req.variables["CUSTOM_VARIABLE"]
])
}
@@ -0,0 +1,4 @@
module.exports = async(context) => {
await new Promise(resolve => setTimeout(resolve, 1000 * 60));
return context.res.send('OK');
};
@@ -1,6 +0,0 @@
<?php
return function ($context) {
sleep(60);
return $context->res->send('OK');
};
@@ -94,5 +94,17 @@ class OriginTest extends TestCase
$this->assertEquals(false, $validator->isValid('appwrite-windows://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Windows platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('chrome-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Chrome Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('moz-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Firefox Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('safari-web-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Safari Extension) platform on your project console dashboard', $validator->getDescription());
$this->assertEquals(false, $validator->isValid('ms-browser-extension://com.company.appname'));
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Edge Extension) platform on your project console dashboard', $validator->getDescription());
}
}
+22
View File
@@ -26,6 +26,15 @@ class URLTest extends TestCase
$this->assertEquals(null, $url['port']);
$this->assertEquals('', $url['path']);
$this->assertEquals('', $url['query']);
$url = URL::parse('appwrite-callback-project://');
$this->assertIsArray($url);
$this->assertEquals('appwrite-callback-project', $url['scheme']);
$this->assertEquals('', $url['host']);
$this->assertEquals(null, $url['port']);
$this->assertEquals('', $url['path']);
$this->assertEquals('', $url['query']);
}
public function testUnparse(): void
@@ -86,6 +95,19 @@ class URLTest extends TestCase
$this->assertIsString($url);
$this->assertEquals('https://eldad:fux@appwrite.io/#bottom', $url);
$url = URL::unparse([
'scheme' => 'https',
'user' => '',
'pass' => '',
'host' => 'appwrite.io',
'port' => null,
'path' => '',
'fragment' => '',
]);
$this->assertIsString($url);
$this->assertEquals('https://appwrite.io/#', $url);
}
public function testParseQuery(): void