diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a8f34743c6..164599f911 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -134,6 +134,14 @@ jobs:
- name: Run General Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_service_test:
name: E2E Service Test
runs-on: ubuntu-latest
@@ -202,6 +210,14 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
runs-on: ubuntu-latest
@@ -282,6 +298,14 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
@@ -315,6 +339,14 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_dev_keys_shared_mode:
name: E2E Shared Mode Service Test (Dev Keys)
runs-on: ubuntu-latest
@@ -362,6 +394,14 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_screenshots_keys:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
@@ -396,6 +436,14 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
+
e2e_screenshots_shared_mode:
name: E2E Shared Mode Service Test (Site Screenshots)
runs-on: ubuntu-latest
@@ -443,3 +491,11 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
+
+ - name: Failure Logs
+ if: failure()
+ run: |
+ echo "=== Appwrite Worker Builds Logs ==="
+ docker compose logs appwrite-worker-builds
+ echo "=== OpenRuntimes Executor Logs ==="
+ docker compose logs openruntimes-executor
diff --git a/README.md b/README.md
index 7768d73015..9cd5808e33 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
+> We just announced Timestamp Overrides for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-timestamp-overrides)
-> We just announced Auto-increment support for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-auto-increment-support)
+> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
> [Get started with Appwrite](https://apwr.dev/appcloud)
diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php
index dce0e39e92..7fc82b7441 100644
--- a/app/config/collections/projects.php
+++ b/app/config/collections/projects.php
@@ -1927,7 +1927,7 @@ return [
'$id' => ID::custom('errors'),
'type' => Database::VAR_STRING,
'format' => '',
- 'size' => 1000000,
+ 'size' => APP_FUNCTION_ERROR_LENGTH_LIMIT,
'signed' => true,
'required' => false,
'default' => null,
@@ -1938,7 +1938,7 @@ return [
'$id' => ID::custom('logs'),
'type' => Database::VAR_STRING,
'format' => '',
- 'size' => 1000000,
+ 'size' => APP_FUNCTION_LOG_LENGTH_LIMIT,
'signed' => true,
'required' => false,
'default' => null,
diff --git a/app/config/templates/function.php b/app/config/templates/function.php
index 9a905c83b1..16838341a9 100644
--- a/app/config/templates/function.php
+++ b/app/config/templates/function.php
@@ -2080,6 +2080,61 @@ return [
'type' => 'text'
]
],
+ 'scopes' => ['databases.read', 'databases.write', 'collections.write', 'attributes.write', 'documents.read', 'documents.write']
+ ],
+ [
+ 'icon' => 'icon-apple',
+ 'id' => 'sign-in-with-apple',
+ 'name' => 'Sign in with Apple',
+ 'score' => 6,
+ 'tagline' => 'Use native Apple sign-in APIs on Apple devices with Appwrite Auth',
+ 'permissions' => ['any'],
+ 'events' => [],
+ 'cron' => '',
+ 'timeout' => 15,
+ 'useCases' => ['auth'],
+ 'runtimes' => [
+ ...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple')
+ ],
+ 'instructions' => 'For documentation and instructions, check out file.',
+ 'vcsProvider' => 'github',
+ 'providerRepositoryId' => 'templates',
+ 'providerOwner' => 'appwrite',
+ 'providerVersion' => '0.2.*',
+ 'variables' => [
+ [
+ 'name' => 'BUNDLE_ID',
+ 'description' => 'Bundle ID of the app. Learn more.',
+ 'value' => '',
+ 'placeholder' => 'com.companyname.appname',
+ 'required' => true,
+ 'type' => 'text'
+ ],
+ [
+ 'name' => 'TEAM_ID',
+ 'description' => 'Team ID of the Apple Developer account.',
+ 'value' => '',
+ 'placeholder' => '6K3...5PH',
+ 'required' => true,
+ 'type' => 'text'
+ ],
+ [
+ 'name' => 'KEY_ID',
+ 'description' => 'Key ID required to communicate with Apple Developer services. Learn more.',
+ 'value' => '',
+ 'placeholder' => '9G8...6YF',
+ 'required' => true,
+ 'type' => 'text'
+ ],
+ [
+ 'name' => 'KEY_CONTENTS_ENCODED',
+ 'description' => 'Contents of Key required to communicated with Apple Developer services, encoded in Base64. Learn more.',
+ 'value' => '',
+ 'placeholder' => '7x8aA...Ab7c',
+ 'required' => true,
+ 'type' => 'password'
+ ]
+ ],
'scopes' => ['users.read', 'users.write']
]
];
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 1eeb10b459..e024bbcd9c 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -1921,7 +1921,7 @@ App::post('/v1/account/tokens/magic-url')
))
->label('abuse-limit', 60)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
- ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
+ ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
@@ -2174,7 +2174,7 @@ App::post('/v1/account/tokens/email')
))
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
- ->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
+ ->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('email', '', new Email(), 'User email.')
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
@@ -2502,7 +2502,7 @@ App::post('/v1/account/tokens/phone')
))
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},phone:{param-phone}', 'url:{url},ip:{ip}'])
- ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
+ ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the phone number has never been used, a new account is created using the provided userId. Otherwise, if the phone number is already attached to an account, the user ID is ignored.')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
->inject('response')
diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php
index 39ebae9590..1dc6ce917b 100644
--- a/app/controllers/api/health.php
+++ b/app/controllers/api/health.php
@@ -23,10 +23,12 @@ use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\System\System;
+use Utopia\Validator\AnyOf;
use Utopia\Validator\Domain;
use Utopia\Validator\Integer;
use Utopia\Validator\Multiple;
use Utopia\Validator\Text;
+use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
App::get('/v1/health')
@@ -397,7 +399,7 @@ App::get('/v1/health/certificate')
],
contentType: ContentType::JSON
))
- ->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
+ ->param('domain', null, new Multiple([new AnyOf([new URL(), new Domain()]), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
->inject('response')
->action(function (string $domain, Response $response) {
if (filter_var($domain, FILTER_VALIDATE_URL)) {
diff --git a/app/controllers/general.php b/app/controllers/general.php
index 0c2a20cadc..ba59050881 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -617,11 +617,33 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
}
+ // Truncate logs if they exceed the limit
+ $maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT;
+ $logs = $executionResponse['logs'] ?? '';
+
+ if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
+ $warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
+ $warningLength = \strlen($warningMessage);
+ $maxContentLength = $maxLogLength - $warningLength;
+ $logs = $warningMessage . \substr($logs, -$maxContentLength);
+ }
+
+ // Truncate errors if they exceed the limit
+ $maxErrorLength = APP_FUNCTION_ERROR_LENGTH_LIMIT;
+ $errors = $executionResponse['errors'] ?? '';
+
+ if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
+ $warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
+ $warningLength = \strlen($warningMessage);
+ $maxContentLength = $maxErrorLength - $warningLength;
+ $errors = $warningMessage . \substr($errors, -$maxContentLength);
+ }
+
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
- $execution->setAttribute('logs', $executionResponse['logs']);
- $execution->setAttribute('errors', $executionResponse['errors']);
+ $execution->setAttribute('logs', $logs);
+ $execution->setAttribute('errors', $errors);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
$execution->setAttribute('duration', $executionResponse['duration']);
diff --git a/app/init/constants.php b/app/init/constants.php
index f2f9f82698..c2d529191b 100644
--- a/app/init/constants.php
+++ b/app/init/constants.php
@@ -143,6 +143,8 @@ const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
+const APP_FUNCTION_LOG_LENGTH_LIMIT = 1000000;
+const APP_FUNCTION_ERROR_LENGTH_LIMIT = 1000000;
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
diff --git a/app/views/general/404.phtml b/app/views/general/404.phtml
index 2a9ff7f312..6dbf158205 100644
--- a/app/views/general/404.phtml
+++ b/app/views/general/404.phtml
@@ -6,9 +6,29 @@
404 Not Found
+
+
+
print($title); ?>
-
+
-
+
print($label); ?>
print($message); ?>
@@ -474,14 +486,14 @@ switch ($type) {
-
+
-
-