mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
Merge branch '1.7.x' into 'fix-users-events'.
This commit is contained in:
commit
a1d74710d1
23 changed files with 441 additions and 51 deletions
56
.github/workflows/tests.yml
vendored
56
.github/workflows/tests.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/dart/sign_in_with_apple">file</a>.',
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.2.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'BUNDLE_ID',
|
||||
'description' => 'Bundle ID of the app. <a class="u-bold" target="_blank" href="https://developer.apple.com/documentation/xcode/preparing-your-app-for-distribution/#Set-the-bundle-ID">Learn more</a>.',
|
||||
'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. <a class="u-bold" target="_blank" href="https://developer.apple.com/help/account/keys/get-a-key-identifier/">Learn more</a>.',
|
||||
'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. <a class="u-bold" target="_blank" href="https://developer.apple.com/help/account/keys/revoke-edit-and-download-keys">Learn more</a>.',
|
||||
'value' => '',
|
||||
'placeholder' => '7x8aA...Ab7c',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
]
|
||||
],
|
||||
'scopes' => ['users.read', 'users.write']
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -6,9 +6,29 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 Not Found</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.appwrite.io/">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('https://fonts.appwrite.io/inter/Inter-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('https://fonts.appwrite.io/inter/Inter-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
src: url('https://fonts.appwrite.io/fira-code/FiraCode-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -104,26 +104,38 @@ switch ($type) {
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
|
||||
<link rel="icon" type="image/svg+xml" href="<?php echo $url; ?>/images/logos/appwrite-icon.svg" />
|
||||
<link rel="mask-icon" type="image/png" href="<?php echo $url; ?>/images/logos/appwrite-icon.png" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/inter/inter-v8-latin-600.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/inter/inter-v8-latin-regular.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.appwrite.io/" crossorigin>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('https://fonts.appwrite.io/inter/Inter-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('https://fonts.appwrite.io/inter/Inter-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
src: url('https://fonts.appwrite.io/fira-code/FiraCode-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<title><?php echo $this->print($title); ?></title>
|
||||
|
||||
<style>
|
||||
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -452,9 +464,9 @@ switch ($type) {
|
|||
</style>
|
||||
</head>
|
||||
|
||||
<body x-data="{ page: 'error' }">
|
||||
<body>
|
||||
<div class="main">
|
||||
<div x-show="page === 'error'" class="content <?php echo $isSimpleMessage ? 'large-error' : 'small-error' ?>">
|
||||
<div id="views-error" class="content <?php echo $isSimpleMessage ? 'large-error' : 'small-error' ?>">
|
||||
<div class="center"><span class="<?php echo $this->print($labelClass); ?>"><?php echo $this->print($label); ?></span></div>
|
||||
<h1><?php echo $this->print($message); ?></h1>
|
||||
<?php if (!empty($type)): ?>
|
||||
|
|
@ -474,14 +486,14 @@ switch ($type) {
|
|||
<?php endif; ?>
|
||||
|
||||
<?php if ($development) : ?>
|
||||
<button class="<?php echo count($buttons) === 0 ? 'bordered-button' : 'button' ?>" x-on:click="page = 'trace'">View error trace</button>
|
||||
<button class="<?php echo count($buttons) === 0 ? 'bordered-button' : 'button' ?>" onclick="openTraceView()">View error trace</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($development) : ?>
|
||||
<div x-show="page === 'trace'" class="error-trace">
|
||||
<button class="back-button" x-on:click="page = 'error'">
|
||||
<div id="views-trace" style="display: none;" class="error-trace">
|
||||
<button class="back-button" onclick="openErrorView()">
|
||||
Back
|
||||
</button>
|
||||
<div class="trace-grid-header">Error trace</div>
|
||||
|
|
@ -512,6 +524,17 @@ switch ($type) {
|
|||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openErrorView() {
|
||||
document.getElementById('views-trace').style.display = 'none';
|
||||
document.getElementById('views-error').style.display = 'block';
|
||||
}
|
||||
|
||||
function openTraceView() {
|
||||
document.getElementById('views-trace').style.display = 'block';
|
||||
document.getElementById('views-error').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -71,6 +71,9 @@ services:
|
|||
- traefik.http.routers.appwrite_api_https.service=appwrite_api
|
||||
- traefik.http.routers.appwrite_api_https.tls=true
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Only needed for tests
|
||||
- ./docker-compose.yml:/usr/src/code/docker-compose.yml # Only needed for tests
|
||||
- ./.env:/usr/src/code/.env # Only needed for tests
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- appwrite-imports:/storage/imports:rw
|
||||
- appwrite-cache:/storage/cache:rw
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.
|
||||
Sends the user an email with a secret key for creating a session. 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**. Then, the user will receive an email with the one-time password. Use the returned user ID and secret and submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.
|
||||
|
||||
A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits).
|
||||
A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits).
|
||||
|
|
|
|||
|
|
@ -423,13 +423,34 @@ class Create extends Base
|
|||
}
|
||||
}
|
||||
|
||||
$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('responseStatusCode', $executionResponse['statusCode']);
|
||||
$execution->setAttribute('responseHeaders', $headersFiltered);
|
||||
$execution->setAttribute('logs', $executionResponse['logs']);
|
||||
$execution->setAttribute('errors', $executionResponse['errors']);
|
||||
$execution->setAttribute('logs', $logs);
|
||||
$execution->setAttribute('errors', $errors);
|
||||
$execution->setAttribute('duration', $executionResponse['duration']);
|
||||
} catch (\Throwable $th) {
|
||||
$durationEnd = \microtime(true);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,8 @@ class Builds extends Action
|
|||
Executor $executor,
|
||||
array $plan
|
||||
): void {
|
||||
Console::log('Build action started');
|
||||
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
|
|
@ -209,6 +211,8 @@ class Builds extends Action
|
|||
Executor $executor,
|
||||
array $plan
|
||||
): void {
|
||||
Console::info('Deployment action started');
|
||||
|
||||
$startTime = DateTime::now();
|
||||
$durationStart = \microtime(true);
|
||||
|
||||
|
|
@ -278,6 +282,8 @@ class Builds extends Action
|
|||
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
|
||||
}
|
||||
|
||||
Console::log('Status marked as processing');
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
|
|
@ -359,6 +365,8 @@ class Builds extends Action
|
|||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
|
||||
Console::log('Template cloned');
|
||||
}
|
||||
} elseif ($isVcsEnabled) {
|
||||
// VCS and VCS+Temaplte
|
||||
|
|
@ -401,6 +409,8 @@ class Builds extends Action
|
|||
throw new \Exception('Unable to clone code repository: ' . $stderr);
|
||||
}
|
||||
|
||||
Console::log('Git repository cloned');
|
||||
|
||||
// Local refactoring for function folder with spaces
|
||||
if (str_contains($rootDirectory, ' ')) {
|
||||
$rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory);
|
||||
|
|
@ -469,6 +479,8 @@ class Builds extends Action
|
|||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
|
||||
Console::log('Git template pushed');
|
||||
}
|
||||
|
||||
$tmpPath = '/tmp/builds/' . $deploymentId;
|
||||
|
|
@ -516,9 +528,13 @@ class Builds extends Action
|
|||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
|
||||
Console::log('Git source uploaded');
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
}
|
||||
|
||||
Console::log('Status marked as building');
|
||||
|
||||
/** Request the executor to build the code... */
|
||||
$deployment->setAttribute('status', 'building');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
|
@ -662,6 +678,8 @@ class Builds extends Action
|
|||
|
||||
$isCanceled = false;
|
||||
|
||||
Console::log('Runtime creation started');
|
||||
|
||||
Co::join([
|
||||
Co\go(function () use ($executor, &$response, $project, $deployment, $source, $resource, $runtime, $vars, $command, $cpus, $memory, $timeout, &$err, $version) {
|
||||
try {
|
||||
|
|
@ -709,7 +727,10 @@ class Builds extends Action
|
|||
command: $command,
|
||||
outputDirectory: $resource->getAttribute('outputDirectory', '')
|
||||
);
|
||||
|
||||
Console::log('createRuntime finished');
|
||||
} catch (\Throwable $error) {
|
||||
Console::warning('createRuntime failed');
|
||||
$err = $error;
|
||||
}
|
||||
}),
|
||||
|
|
@ -798,7 +819,9 @@ class Builds extends Action
|
|||
}
|
||||
}
|
||||
);
|
||||
Console::warning('listLogs finished');
|
||||
} catch (\Throwable $error) {
|
||||
Console::warning('listLogs failed');
|
||||
if (empty($err)) {
|
||||
$err = $error;
|
||||
}
|
||||
|
|
@ -806,6 +829,8 @@ class Builds extends Action
|
|||
}),
|
||||
]);
|
||||
|
||||
Console::log('Runtime creation finished');
|
||||
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
|
||||
return;
|
||||
|
|
@ -860,6 +885,8 @@ class Builds extends Action
|
|||
|
||||
$deployment->setAttribute('adapter', $detection->getName());
|
||||
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
|
||||
|
||||
Console::log('Adapter detected');
|
||||
} elseif ($adapter === 'ssr' && $detection->getName() === 'static') {
|
||||
throw new \Exception('Adapter mismatch. Detected: ' . $detection->getName() . ' does not match with the set adapter: ' . $adapter);
|
||||
}
|
||||
|
|
@ -870,10 +897,15 @@ class Builds extends Action
|
|||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
|
||||
Console::log('Build details stored');
|
||||
|
||||
$this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment);
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
|
||||
/** Screenshot site */
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
Console::log('Site screenshot started');
|
||||
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][97m Screenshot capturing started. [0m\n";
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
|
|
@ -881,10 +913,7 @@ class Builds extends Action
|
|||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/** Screenshot site */
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
try {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal("projectInternalId", [$project->getSequence()]),
|
||||
|
|
@ -1049,6 +1078,8 @@ class Builds extends Action
|
|||
$deployment->setAttribute('buildLogs', $logs);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
}
|
||||
|
||||
Console::log('Site screenshot finished');
|
||||
}
|
||||
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
|
|
@ -1060,6 +1091,8 @@ class Builds extends Action
|
|||
$deployment->setAttribute('status', 'ready');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
|
||||
Console::log('Status marked as ready');
|
||||
|
||||
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
|
||||
}
|
||||
|
|
@ -1072,8 +1105,6 @@ class Builds extends Action
|
|||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
}
|
||||
|
||||
Console::success("Build id: $deploymentId created");
|
||||
|
||||
/** Set auto deploy */
|
||||
$activateBuild = false;
|
||||
if ($deployment->getAttribute('activate') === true) {
|
||||
|
|
@ -1153,6 +1184,8 @@ class Builds extends Action
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
Console::log('Deployment activated');
|
||||
}
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
|
|
@ -1211,6 +1244,8 @@ class Builds extends Action
|
|||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
]));
|
||||
}, $queries);
|
||||
|
||||
Console::log('Preview rule created');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1228,6 +1263,8 @@ class Builds extends Action
|
|||
return;
|
||||
}
|
||||
|
||||
Console::log('Build duration updated');
|
||||
|
||||
/** Update function schedule */
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
|
|
@ -1239,6 +1276,8 @@ class Builds extends Action
|
|||
->setAttribute('active', !empty($resource->getAttribute('schedule')) && !empty($resource->getAttribute('deploymentId')));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
}
|
||||
|
||||
Console::info('Deployment action finished');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Build failed:');
|
||||
Console::error($th->getMessage());
|
||||
|
|
|
|||
|
|
@ -550,14 +550,36 @@ class Functions extends Action
|
|||
}
|
||||
}
|
||||
|
||||
$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 */
|
||||
$execution
|
||||
->setAttribute('status', $status)
|
||||
->setAttribute('responseStatusCode', $executionResponse['statusCode'])
|
||||
->setAttribute('responseHeaders', $headersFiltered)
|
||||
->setAttribute('logs', $executionResponse['logs'])
|
||||
->setAttribute('errors', $executionResponse['errors'])
|
||||
->setAttribute('logs', $logs)
|
||||
->setAttribute('errors', $errors)
|
||||
->setAttribute('duration', $executionResponse['duration']);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$durationEnd = \microtime(true);
|
||||
$execution
|
||||
|
|
|
|||
|
|
@ -2282,4 +2282,45 @@ class FunctionsCustomServerTest extends Scope
|
|||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testLogAndErrorTruncation(): void
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test Log Truncation',
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'timeout' => 15,
|
||||
]);
|
||||
|
||||
$this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('log-error-truncation'),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$execution = $this->createExecution($functionId, [
|
||||
'async' => 'false'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertEquals(200, $execution['body']['responseStatusCode']);
|
||||
|
||||
// Verify logs are truncated and warning message is present at the beginning
|
||||
$logs = $execution['body']['logs'];
|
||||
$this->assertLessThanOrEqual(APP_FUNCTION_LOG_LENGTH_LIMIT, strlen($logs));
|
||||
$this->assertStringStartsWith('[WARNING] Logs truncated', $logs);
|
||||
|
||||
$this->assertStringNotContainsString('z', $logs);
|
||||
$this->assertStringContainsString('a', $logs);
|
||||
|
||||
// Verify errors are truncated and warning message is present at the beginning
|
||||
$errors = $execution['body']['errors'];
|
||||
$this->assertLessThanOrEqual(APP_FUNCTION_ERROR_LENGTH_LIMIT, strlen($errors));
|
||||
$this->assertStringStartsWith('[WARNING] Errors truncated', $errors);
|
||||
|
||||
$this->assertStringNotContainsString('z', $errors);
|
||||
$this->assertStringContainsString('a', $errors);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,15 +109,11 @@ class ProxyCustomServerTest extends Scope
|
|||
$rule = $this->createAPIRule('https://' . $domain);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
// Unexpected I would say, but it is the current behaviour
|
||||
$rule = $this->createAPIRule('wss://' . $domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
// Unexpected I would say, but it is the current behaviour
|
||||
$rule = $this->createAPIRule($domain . '/some-path');
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateRedirectRule(): void
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests\E2E\Services\Sites;
|
||||
|
||||
use Appwrite\Tests\Async;
|
||||
use Appwrite\Tests\Async\Exceptions\Critical;
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\CLI\Console;
|
||||
|
|
@ -48,8 +49,23 @@ trait SitesBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
|
||||
if ($deployment['body']['status'] === 'failed') {
|
||||
throw new Critical('Deployment failed: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
Console::execute("docker inspect openruntimes-executor --format='{{.State.ExitCode}}'", '', $this->stdout, $this->stderr);
|
||||
if (\trim($this->stdout) !== '0') {
|
||||
$msg = 'Executor has a problem: ' . $this->stderr . ' (' . $this->stdout . '), current status: ';
|
||||
|
||||
Console::execute("docker compose logs openruntimes-executor", '', $this->stdout, $this->stderr);
|
||||
$msg .= $this->stdout . ' (' . $this->stderr . ')';
|
||||
|
||||
throw new Critical($msg . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
$this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
}, 150000, 500);
|
||||
}, 300000, 500);
|
||||
|
||||
// Not === so multipart/form-data works fine too
|
||||
if (($params['activate'] ?? false) == true) {
|
||||
|
|
|
|||
|
|
@ -405,8 +405,10 @@ class SitesCustomServerTest extends Scope
|
|||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('ssr', $site['body']['adapter']);
|
||||
$this->assertEventually(function () use ($siteId, &$site) {
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('ssr', $site['body']['adapter']);
|
||||
});
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
|
@ -416,6 +418,7 @@ class SitesCustomServerTest extends Scope
|
|||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
#[Retry(count: 3)]
|
||||
public function testAdapterDetectionAstroStatic(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
|
|
@ -1507,6 +1510,7 @@ class SitesCustomServerTest extends Scope
|
|||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
#[Retry(count: 3)]
|
||||
public function testSiteTemplate(): void
|
||||
{
|
||||
$template = $this->getTemplate('playground-for-astro');
|
||||
|
|
@ -2693,4 +2697,43 @@ class SitesCustomServerTest extends Scope
|
|||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testCookieHeader()
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/cookies', [
|
||||
'cookie' => 'custom-session-id=abcd123'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals("abcd123", $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Appwrite\Tests\Async;
|
||||
|
||||
use Appwrite\Tests\Async\Exceptions\Critical;
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
|
||||
final class Eventually extends Constraint
|
||||
|
|
@ -23,6 +24,8 @@ final class Eventually extends Constraint
|
|||
try {
|
||||
$probe();
|
||||
return true;
|
||||
} catch (Critical $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
$lastException = $exception;
|
||||
}
|
||||
|
|
|
|||
7
tests/extensions/Async/Exceptions/Critical.php
Normal file
7
tests/extensions/Async/Exceptions/Critical.php
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests\Async\Exceptions;
|
||||
|
||||
class Critical extends \Exception
|
||||
{
|
||||
}
|
||||
14
tests/resources/functions/log-error-truncation/index.js
Normal file
14
tests/resources/functions/log-error-truncation/index.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = async(context) => {
|
||||
// Create a string that is 1000001 characters long (exceeds the 1000000 limit)
|
||||
const longString = 'z' + 'a'.repeat(1000000);
|
||||
|
||||
context.log(longString);
|
||||
context.error(longString);
|
||||
|
||||
return context.res.json({
|
||||
motto: 'Build like a team of hundreds_',
|
||||
learn: 'https://appwrite.io/docs',
|
||||
connect: 'https://appwrite.io/discord',
|
||||
getInspired: 'https://builtwith.appwrite.io',
|
||||
});
|
||||
};
|
||||
4
tests/resources/sites/astro/src/pages/cookies.js
Normal file
4
tests/resources/sites/astro/src/pages/cookies.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export async function GET(context) {
|
||||
const sessionId = context.cookies.get("custom-session-id")?.value ?? 'Custom session ID missing';
|
||||
return new Response(sessionId);
|
||||
}
|
||||
Loading…
Reference in a new issue