diff --git a/.gitignore b/.gitignore
index ef05c948ff..5ae03e2a56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
!/.idea/php.xml
.DS_Store
.php_cs.cache
+.phpactor.json
debug/
app/sdks
dev/yasd_init.php
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index c9a244dd65..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,4 +0,0 @@
-[submodule "app/console"]
- path = app/console
- url = https://github.com/appwrite/console
- branch = 1.6.x
diff --git a/Dockerfile b/Dockerfile
index cf15d4c6c1..fc501701ba 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,23 +12,6 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-FROM --platform=$BUILDPLATFORM node:20.11.0-alpine3.19 as node
-
-COPY app/console /usr/local/src/console
-
-WORKDIR /usr/local/src/console
-
-ARG VITE_GA_PROJECT
-ARG VITE_CONSOLE_MODE
-ARG VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
-
-ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
-ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
-ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
-
-RUN npm ci
-RUN npm run build
-
FROM appwrite/base:0.9.1 as final
LABEL maintainer="team@appwrite.io"
@@ -48,7 +31,6 @@ RUN \
WORKDIR /usr/src/code
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
-COPY --from=node /usr/local/src/console/build /usr/src/code/console
# Add Source Code
COPY ./app /usr/src/code/app
diff --git a/app/config/collections.php b/app/config/collections.php
index f282ffee18..5e7de4da81 100644
--- a/app/config/collections.php
+++ b/app/config/collections.php
@@ -3848,6 +3848,39 @@ $projectCollections = array_merge([
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('scheduledAt'),
+ 'type' => Database::VAR_DATETIME,
+ 'format' => '',
+ 'size' => 0,
+ 'signed' => false,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => ['datetime'],
+ ],
+ [
+ '$id' => ID::custom('scheduleInternalId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('scheduleId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
],
'indexes' => [
[
@@ -4751,8 +4784,8 @@ $consoleCollections = array_merge([
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
- 'required' => true,
- 'default' => null,
+ 'required' => false,
+ 'default' => [],
'array' => true,
'filters' => [],
],
diff --git a/app/config/platforms.php b/app/config/platforms.php
index 7b0e315902..3a77b99887 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -221,7 +221,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
- 'version' => '6.0.0-rc.3',
+ 'version' => '6.0.0-rc.4',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
diff --git a/app/console b/app/console
deleted file mode 160000
index f978cecfaf..0000000000
--- a/app/console
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f978cecfafe55e85fcd20fe90fc020e78a7c7952
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
index d2a1790d94..e4a031383f 100644
--- a/app/controllers/api/functions.php
+++ b/app/controllers/api/functions.php
@@ -1744,9 +1744,8 @@ App::post('/v1/functions/:functionId/executions')
->setContext('function', $function);
if ($async) {
- $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
-
if(is_null($scheduledAt)) {
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
$queueForFunctions
->setType('http')
->setExecution($execution)
@@ -1770,7 +1769,7 @@ App::post('/v1/functions/:functionId/executions')
'jwt' => $jwt,
];
- $dbForConsole->createDocument('schedules', new Document([
+ $schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => System::getEnv('_APP_REGION', 'default'),
'resourceType' => ScheduleExecutions::getSupportedResource(),
'resourceId' => $execution->getId(),
@@ -1781,6 +1780,13 @@ App::post('/v1/functions/:functionId/executions')
'data' => $data,
'active' => true,
]));
+
+ $execution = $execution
+ ->setAttribute('scheduleId', $schedule->getId())
+ ->setAttribute('scheduleInternalId', $schedule->getInternalId())
+ ->setAttribute('scheduledAt', $scheduledAt);
+
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
return $response
@@ -1825,7 +1831,8 @@ App::post('/v1/functions/:functionId/executions')
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
- 'APPWRITE_VERSION' => APP_VERSION_STABLE
+ 'APPWRITE_VERSION' => APP_VERSION_STABLE,
+ 'APPWRITE_REGION' => $project->getAttribute('region'),
]);
/** Execute function */
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 652644d44b..b080066653 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -63,7 +63,7 @@ App::post('/v1/storage/buckets')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
- ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
+ ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
@@ -240,7 +240,7 @@ App::put('/v1/storage/buckets/:bucketId')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
- ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
+ ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index 8b32349957..78988f525b 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -2109,7 +2109,7 @@ App::post('/v1/users/:userId/jwts')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->param('userId', '', new UID(), 'User ID.')
- ->param('sessionId', 'recent', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
+ ->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForProject')
@@ -2137,17 +2137,13 @@ App::post('/v1/users/:userId/jwts')
}
}
- if ($session->isEmpty()) {
- throw new Exception(Exception::USER_SESSION_NOT_FOUND);
- }
-
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => $jwt->encode([
'userId' => $user->getId(),
- 'sessionId' => $session->getId()
+ 'sessionId' => $session->isEmpty() ? '' : $session->getId()
])]), Response::MODEL_JWT);
});
diff --git a/app/controllers/general.php b/app/controllers/general.php
index 0ed7d9ff40..143eb15684 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -2,6 +2,7 @@
require_once __DIR__ . '/../init.php';
+use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
@@ -12,9 +13,11 @@ use Appwrite\Platform\Appwrite;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
+use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
+use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
@@ -165,7 +168,15 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
}
+ $jwtExpiry = $function->getAttribute('timeout', 900);
+ $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
+ $apiKey = $jwtObj->encode([
+ 'projectId' => $project->getId(),
+ 'scopes' => $function->getAttribute('scopes', [])
+ ]);
+
$headers = \array_merge([], $requestHeaders);
+ $headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = '';
$headers['x-appwrite-user-jwt'] = '';
@@ -244,15 +255,21 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
+ $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
+ $hostname = System::getEnv('_APP_DOMAIN');
+ $endpoint = $protocol . '://' . $hostname . "/v1";
+
// Appwrite vars
$vars = \array_merge($vars, [
+ 'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
- 'APPWRITE_VERSION' => APP_VERSION_STABLE
+ 'APPWRITE_VERSION' => APP_VERSION_STABLE,
+ 'APPWRITE_REGION' => $project->getAttribute('region'),
]);
/** Execute function */
@@ -436,6 +453,9 @@ App::init()
if (version_compare($requestFormat, '1.5.0', '<')) {
$request->addFilter(new RequestV17());
}
+ if (version_compare($requestFormat, '1.6.0', '<')) {
+ $request->addFilter(new RequestV18());
+ }
}
$domain = $request->getHostname();
@@ -552,6 +572,9 @@ App::init()
if (version_compare($responseFormat, '1.5.0', '<')) {
$response->addFilter(new ResponseV17());
}
+ if (version_compare($responseFormat, '1.6.0', '<')) {
+ $response->addFilter(new ResponseV18());
+ }
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
}
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index 713cb83801..158cec98e2 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -206,6 +206,7 @@ App::init()
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
+ // Remove after migration
if(!\str_contains($apiKey, '_')) {
$keyType = API_KEY_STANDARD;
$authKey = $apiKey;
diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php
index fac8d21261..a4c76a9a35 100644
--- a/app/controllers/web/console.php
+++ b/app/controllers/web/console.php
@@ -16,8 +16,7 @@ App::init()
;
});
-App::get('/console/*')
- ->alias('/')
+App::get('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
@@ -31,45 +30,13 @@ App::get('/console/*')
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
- $fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
-
- // Card SSR
- if (\str_starts_with($request->getURI(), '/card')) {
- $urlCunks = \explode('/', $request->getURI());
- $userId = $urlCunks[\count($urlCunks) - 1] ?? '';
-
- $domain = $request->getProtocol() . '://' . $request->getHostname();
-
- if (!empty($userId)) {
- $ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId;
- } else {
- $ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal';
- }
-
- $ogTags = [
- '
Appwrite Cloud Card',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- ];
-
- $fallback = \str_replace('', \implode('', $ogTags), $fallback);
+ $url = parse_url($request->getURI());
+ $target = "/console{$url['path']}";
+ if ($url['query'] ?? false) {
+ $target .= "?{$url['query']}";
}
-
- $response->html($fallback);
+ if ($url['fragment'] ?? false) {
+ $target .= "#{$url['fragment']}";
+ }
+ $response->redirect($target);
});
diff --git a/app/http.php b/app/http.php
index f410d98a0c..ac40639057 100644
--- a/app/http.php
+++ b/app/http.php
@@ -57,8 +57,6 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
-Files::load(__DIR__ . '/../console');
-
include __DIR__ . '/controllers/general.php';
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
diff --git a/app/init.php b/app/init.php
index 0d283464c4..0ddeecc341 100644
--- a/app/init.php
+++ b/app/init.php
@@ -118,7 +118,7 @@ const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4314;
-const APP_VERSION_STABLE = '1.5.7';
+const APP_VERSION_STABLE = '1.6.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -1243,14 +1243,15 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
}
$jwtUserId = $payload['userId'] ?? '';
- $jwtSessionId = $payload['sessionId'] ?? '';
-
- if ($jwtUserId && $jwtSessionId) {
+ if (!empty($jwtUserId)) {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
- if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
- $user = new Document([]);
+ $jwtSessionId = $payload['sessionId'] ?? '';
+ if(!empty($jwtSessionId)) {
+ if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
+ $user = new Document([]);
+ }
}
}
diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml
index fb88db4f39..70c6fbdebd 100644
--- a/app/views/install/compose.phtml
+++ b/app/views/install/compose.phtml
@@ -163,6 +163,28 @@ $image = $this->getParam('image', '');
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
+ appwrite-console:
+ <<: *x-logging
+ container_name: appwrite-console
+ image: /console:appwrite/console:5.0.0-rc.11
+ restart: unless-stopped
+ networks:
+ - appwrite
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ - "traefik.docker.network=appwrite"
+ - "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
+ #ws
+ - traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
+ - traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
+ - traefik.http.routers.appwrite_console_http.service=appwrite_console
+ # wss
+ - traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
+ - traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
+ - traefik.http.routers.appwrite_console_https.service=appwrite_console
+ - traefik.http.routers.appwrite_console_https.tls=true
+
appwrite-realtime:
image: /:
entrypoint: realtime
diff --git a/composer.lock b/composer.lock
index 8d307c5c99..68f109ed72 100644
--- a/composer.lock
+++ b/composer.lock
@@ -356,16 +356,16 @@
},
{
"name": "chillerlan/php-settings-container",
- "version": "2.1.5",
+ "version": "2.1.6",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
- "reference": "f705310389264c3578fdd9ffb15aa2cd6d91772e"
+ "reference": "5553558bd381fce5108c6d0343c12e488cfec6bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/f705310389264c3578fdd9ffb15aa2cd6d91772e",
- "reference": "f705310389264c3578fdd9ffb15aa2cd6d91772e",
+ "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/5553558bd381fce5108c6d0343c12e488cfec6bb",
+ "reference": "5553558bd381fce5108c6d0343c12e488cfec6bb",
"shasum": ""
},
"require": {
@@ -373,15 +373,16 @@
"php": "^7.4 || ^8.0"
},
"require-dev": {
- "phan/phan": "^5.4",
- "phpcsstandards/php_codesniffer": "^3.8",
- "phpmd/phpmd": "^2.13",
- "phpunit/phpunit": "^9.6"
+ "phpmd/phpmd": "^2.15",
+ "phpstan/phpstan": "^1.11",
+ "phpstan/phpstan-deprecation-rules": "^1.2",
+ "phpunit/phpunit": "^9.6",
+ "squizlabs/php_codesniffer": "^3.10"
},
"type": "library",
"autoload": {
"psr-4": {
- "chillerlan\\Settings\\": "src/"
+ "chillerlan\\Settings\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -418,7 +419,7 @@
"type": "ko_fi"
}
],
- "time": "2024-01-05T23:20:55+00:00"
+ "time": "2024-07-17T01:04:28+00:00"
},
{
"name": "dragonmantank/cron-expression",
@@ -2989,16 +2990,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
- "version": "0.39.2",
+ "version": "0.39.3",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
- "reference": "550b56aa7ee3f98319b79a20d2478e68a1b5e9bd"
+ "reference": "16142d88270e368030d7956cadf2d7816413f8c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/550b56aa7ee3f98319b79a20d2478e68a1b5e9bd",
- "reference": "550b56aa7ee3f98319b79a20d2478e68a1b5e9bd",
+ "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/16142d88270e368030d7956cadf2d7816413f8c4",
+ "reference": "16142d88270e368030d7956cadf2d7816413f8c4",
"shasum": ""
},
"require": {
@@ -3034,9 +3035,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
- "source": "https://github.com/appwrite/sdk-generator/tree/0.39.2"
+ "source": "https://github.com/appwrite/sdk-generator/tree/0.39.3"
},
- "time": "2024-07-10T11:23:54+00:00"
+ "time": "2024-07-12T15:29:48+00:00"
},
{
"name": "doctrine/deprecations",
diff --git a/docker-compose.yml b/docker-compose.yml
index 7be1225eaa..d28586bf7b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -52,7 +52,7 @@ services:
DEBUG: false
TESTING: true
VERSION: dev
- ports:
+ ports:
- 9501:80
networks:
- appwrite
@@ -192,6 +192,28 @@ services:
- _APP_EXPERIMENT_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
+ appwrite-console:
+ <<: *x-logging
+ container_name: appwrite-console
+ image: appwrite/console:5.0.0-rc.11
+ restart: unless-stopped
+ networks:
+ - appwrite
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ - "traefik.docker.network=appwrite"
+ - "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
+ #ws
+ - traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
+ - traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
+ - traefik.http.routers.appwrite_console_http.service=appwrite_console
+ # wss
+ - traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
+ - traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
+ - traefik.http.routers.appwrite_console_https.service=appwrite_console
+ - traefik.http.routers.appwrite_console_https.tls=true
+
appwrite-realtime:
entrypoint: realtime
<<: *x-logging
diff --git a/docs/examples/1.6.x/console-cli/examples/account/delete-mfa-authenticator.md b/docs/examples/1.6.x/console-cli/examples/account/delete-mfa-authenticator.md
index c36e9d5578..d4664855b5 100644
--- a/docs/examples/1.6.x/console-cli/examples/account/delete-mfa-authenticator.md
+++ b/docs/examples/1.6.x/console-cli/examples/account/delete-mfa-authenticator.md
@@ -1,3 +1,2 @@
appwrite account deleteMfaAuthenticator \
- --type totp \
- --otp
+ --type totp
diff --git a/docs/references/avatars/get-favicon.md b/docs/references/avatars/get-favicon.md
index b571e0af93..c3a63dd83f 100644
--- a/docs/references/avatars/get-favicon.md
+++ b/docs/references/avatars/get-favicon.md
@@ -1 +1,3 @@
Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.
+
+This endpoint does not follow HTTP redirects.
\ No newline at end of file
diff --git a/docs/references/avatars/get-image.md b/docs/references/avatars/get-image.md
index efd654b362..ca9cc5e418 100644
--- a/docs/references/avatars/get-image.md
+++ b/docs/references/avatars/get-image.md
@@ -1,3 +1,5 @@
Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.
+
+This endpoint does not follow HTTP redirects.
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 90ebd4225f..e772866051 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -6,7 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
- stopOnFailure="false"
+ stopOnFailure="true"
>
diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php
index e3a2021c1a..9075b85702 100644
--- a/src/Appwrite/Migration/Migration.php
+++ b/src/Appwrite/Migration/Migration.php
@@ -86,6 +86,7 @@ abstract class Migration
'1.5.5' => 'V20',
'1.5.6' => 'V20',
'1.5.7' => 'V20',
+ '1.6.0' => 'V21'
];
/**
diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php
new file mode 100644
index 0000000000..7dd2912234
--- /dev/null
+++ b/src/Appwrite/Migration/Version/V21.php
@@ -0,0 +1,180 @@
+ null,
+ fn () => []
+ );
+ }
+
+ Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
+ $this->projectDB->setNamespace("_{$this->project->getInternalId()}");
+
+ Console::info('Migrating Collections');
+ $this->migrateCollections();
+
+ Console::info('Migrating Documents');
+ $this->forEachDocument([$this, 'fixDocument']);
+ }
+
+ /**
+ * Migrate Collections.
+ *
+ * @return void
+ * @throws Exception|Throwable
+ */
+ private function migrateCollections(): void
+ {
+ $internalProjectId = $this->project->getInternalId();
+ $collectionType = match ($internalProjectId) {
+ 'console' => 'console',
+ default => 'projects',
+ };
+
+ $collections = $this->collections[$collectionType];
+ foreach ($collections as $collection) {
+ $id = $collection['$id'];
+
+ Console::log("Migrating Collection \"{$id}\"");
+
+ $this->projectDB->setNamespace("_$internalProjectId");
+
+ switch ($id) {
+ case 'projects':
+ // Create accessedAt attribute
+ try {
+ $this->createAttributeFromCollection($this->projectDB, $id, 'accessedAt');
+ } catch (Throwable $th) {
+ Console::warning("'accessedAt' from {$id}: {$th->getMessage()}");
+ }
+ break;
+ case 'schedules':
+ // Create data attribute
+ try {
+ $this->createAttributeFromCollection($this->projectDB, $id, 'data');
+ } catch (Throwable $th) {
+ Console::warning("'data' from {$id}: {$th->getMessage()}");
+ }
+
+ break;
+
+ case 'functions':
+ // Create scopes attribute
+ try {
+ $this->createAttributeFromCollection($this->projectDB, $id, 'scopes');
+ } catch (Throwable $th) {
+ Console::warning("'scopes' from {$id}: {$th->getMessage()}");
+ }
+
+ // Create size attribute
+ try {
+ $this->createAttributeFromCollection($this->projectDB, $id, 'size');
+ } catch (Throwable $th) {
+ Console::warning("'size' from {$id}: {$th->getMessage()}");
+ }
+
+ break;
+ case 'executions':
+ // Create requestMethod index
+ try {
+ $this->createIndexFromCollection($this->projectDB, $id, '_key_requestMethod');
+ } catch (\Throwable $th) {
+ Console::warning("'_key_requestMethod' from {$id}: {$th->getMessage()}");
+ }
+
+ // Create requestPath index
+ try {
+ $this->createIndexFromCollection($this->projectDB, $id, '_key_requestPath');
+ } catch (\Throwable $th) {
+ Console::warning("'_key_requestPath' from {$id}: {$th->getMessage()}");
+ }
+
+ // Create deployment index
+ try {
+ $this->createIndexFromCollection($this->projectDB, $id, '_key_deployment');
+ } catch (\Throwable $th) {
+ Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}");
+ }
+
+ try {
+ /**
+ * Create 'scheduledAt' attribute
+ */
+ $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt');
+ } catch (\Throwable $th) {
+ Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}");
+ }
+
+ try {
+ /**
+ * Create 'scheduleInternalId' attribute
+ */
+ $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId');
+ } catch (\Throwable $th) {
+ Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}");
+ }
+
+ try {
+ /**
+ * Create 'scheduleId' attribute
+ */
+ $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId');
+ } catch (\Throwable $th) {
+ Console::warning("'scheduleId' from {$id}: {$th->getMessage()}");
+ }
+ }
+
+ usleep(50000);
+ }
+ }
+
+ /**
+ * Fix run on each document
+ *
+ * @param Document $document
+ * @return Document
+ */
+ protected function fixDocument(Document $document): Document
+ {
+ switch ($document->getCollection()) {
+ case 'projects':
+ /**
+ * Bump version number.
+ */
+ $document->setAttribute('version', '1.6.0');
+
+ // Add accessedAt attribute
+ $document->setAttribute('accessedAt', DateTime::now());
+ break;
+ case 'functions':
+ // Add scopes attribute
+ $document->setAttribute('scopes', []);
+
+ // Add size attribute
+ $document->setAttribute('size', 's-1vcpu-512m');
+ }
+
+ return $document;
+ }
+}
diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php
index 922498c3fa..b712edd402 100644
--- a/src/Appwrite/Platform/Workers/Builds.php
+++ b/src/Appwrite/Platform/Workers/Builds.php
@@ -400,7 +400,8 @@ class Builds extends Action
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
- 'APPWRITE_VERSION' => APP_VERSION_STABLE
+ 'APPWRITE_VERSION' => APP_VERSION_STABLE,
+ 'APPWRITE_REGION' => $project->getAttribute('region'),
]);
$command = $deployment->getAttribute('commands', '');
diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php
index 5dfdc0a63a..78671bfeb0 100644
--- a/src/Appwrite/Platform/Workers/Functions.php
+++ b/src/Appwrite/Platform/Workers/Functions.php
@@ -373,6 +373,9 @@ class Functions extends Action
$headers['x-appwrite-event'] = $event ?? '';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
+ $headers['x-appwrite-country-code'] = '';
+ $headers['x-appwrite-continent-code'] = '';
+ $headers['x-appwrite-continent-eu'] = 'false';
/** Create execution or update execution status */
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
@@ -464,7 +467,8 @@ class Functions extends Action
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
- 'APPWRITE_VERSION' => APP_VERSION_STABLE
+ 'APPWRITE_VERSION' => APP_VERSION_STABLE,
+ 'APPWRITE_REGION' => $project->getAttribute('region'),
]);
/** Execute function */
diff --git a/src/Appwrite/Utopia/Request/Filters/V18.php b/src/Appwrite/Utopia/Request/Filters/V18.php
new file mode 100644
index 0000000000..4f889ce66b
--- /dev/null
+++ b/src/Appwrite/Utopia/Request/Filters/V18.php
@@ -0,0 +1,21 @@
+ $this->parseFunction($content),
+ Response::MODEL_EXECUTION => $this->parseExecution($content),
+ Response::MODEL_PROJECT => $this->parseProject($content),
+ default => $parsedResponse,
+ };
+
+ return $parsedResponse;
+ }
+
+ protected function parseExecution(array $content)
+ {
+ unset($content['scheduledAt']);
+ return $content;
+ }
+
+ protected function parseFunction(array $content)
+ {
+ unset($content['scopes']);
+ return $content;
+ }
+
+ protected function parseProject(array $content)
+ {
+ unset($content['authMockNumbers']);
+ unset($content['authSessionAlerts']);
+ return $content;
+ }
+}
diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php
index fbd9619a40..90fbdc9689 100644
--- a/src/Appwrite/Utopia/Response/Model/Execution.php
+++ b/src/Appwrite/Utopia/Response/Model/Execution.php
@@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
+use Utopia\Database\DateTime;
use Utopia\Database\Helpers\Role;
class Execution extends Model
@@ -110,6 +111,13 @@ class Execution extends Model
'default' => 0,
'example' => 0.400,
])
+ ->addRule('scheduledAt', [
+ 'type' => self::TYPE_DATETIME,
+ 'description' => 'The scheduled time for execution. If left empty, execution will be queued immediately.',
+ 'required' => false,
+ 'default' => DateTime::now(),
+ 'example' => self::TYPE_DATETIME_EXAMPLE,
+ ])
;
}
diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php
index 29ed756932..3206381e95 100644
--- a/src/Executor/Executor.php
+++ b/src/Executor/Executor.php
@@ -75,6 +75,12 @@ class Executor
$runtimeId = "$projectId-$deploymentId-build";
$route = "/runtimes";
$timeout = (int) System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
+
+ // Remove after migration
+ if ($version == 'v3') {
+ $version = 'v4';
+ }
+
$params = [
'runtimeId' => $runtimeId,
'source' => $source,
@@ -188,6 +194,13 @@ class Executor
$runtimeId = "$projectId-$deploymentId";
$route = '/runtimes/' . $runtimeId . '/execution';
+
+
+ // Remove after migration
+ if ($version == 'v3') {
+ $version = 'v4';
+ }
+
$params = [
'runtimeId' => $runtimeId,
'variables' => $variables,
diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php
index 92bc52561c..5c3b10a9ca 100644
--- a/tests/e2e/General/HTTPTest.php
+++ b/tests/e2e/General/HTTPTest.php
@@ -15,7 +15,7 @@ class HTTPTest extends Scope
public function setUp(): void
{
parent::setUp();
- $this->client->setEndpoint('http://localhost');
+ $this->client->setEndpoint('http://traefik');
}
public function testOptions()
@@ -163,11 +163,11 @@ class HTTPTest extends Scope
public function testDefaultOAuth2()
{
- $response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/success', $this->getHeaders());
+ $response = $this->client->call(Client::METHOD_GET, '/console/auth/oauth2/success', $this->getHeaders());
$this->assertEquals(200, $response['headers']['status-code']);
- $response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/failure', $this->getHeaders());
+ $response = $this->client->call(Client::METHOD_GET, '/console/auth/oauth2/failure', $this->getHeaders());
$this->assertEquals(200, $response['headers']['status-code']);
}
diff --git a/tests/e2e/General/HooksTest.php b/tests/e2e/General/HooksTest.php
index d67c3256d9..af6ccab0d5 100644
--- a/tests/e2e/General/HooksTest.php
+++ b/tests/e2e/General/HooksTest.php
@@ -17,7 +17,7 @@ class HooksTest extends Scope
public function setUp(): void
{
parent::setUp();
- $this->client->setEndpoint('http://localhost');
+ $this->client->setEndpoint('http://traefik');
}
public function testProjectHooks()
diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
index bf969d388a..4e501692fa 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
@@ -8,6 +8,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
+use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
@@ -269,6 +270,7 @@ class FunctionsCustomClientTest extends Scope
// Schedule execution for the future
\date_default_timezone_set('UTC');
$futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s');
+ $futureTimeIso = DateTime::formatTz($futureTime);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
@@ -286,9 +288,23 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertEquals('scheduled', $execution['body']['status']);
+ $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']);
$executionId = $execution['body']['$id'];
+ // List executions and ensure it has schedule date
+ $response = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]);
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertGreaterThan(0, \count($response['body']['executions']));
+ $recentExecution = $response['body']['executions'][0];
+ $this->assertEquals($executionId, $recentExecution['$id']);
+ $this->assertEquals($futureTimeIso, $recentExecution['scheduledAt']);
+
sleep(20);
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [
@@ -303,6 +319,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('/custom', $execution['body']['requestPath']);
$this->assertEquals('GET', $execution['body']['requestMethod']);
$this->assertGreaterThan(0, $execution['body']['duration']);
+ $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']);
/* Test for FAILURE */
@@ -449,6 +466,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
+ $this->assertEquals('default', $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
@@ -834,6 +852,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
+ $this->assertEquals('default', $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php
index c34227e4de..c06bc6f4a8 100644
--- a/tests/e2e/Services/Users/UsersBase.php
+++ b/tests/e2e/Services/Users/UsersBase.php
@@ -1589,6 +1589,27 @@ trait UsersBase
], false);
$this->assertEquals($user['headers']['status-code'], 201);
+ // Create JWT 0, with no session available
+ $response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), []);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']['jwt']);
+ $jwt0 = $response['body']['jwt'];
+
+ // Ensure JWT 0 works
+ $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-jwt' => $jwt0,
+ ]));
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($userId, $response['body']['$id']);
+
// Create two sessions
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
@@ -1641,12 +1662,13 @@ trait UsersBase
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($userId, $response['body']['$id']);
- // Create JWT 2 for latest session using default param
+ // Create JWT 2 for latest session using 'current' param
$response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
- 'duration' => 5
+ 'duration' => 5,
+ 'sessionId' => 'current'
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -1696,6 +1718,27 @@ trait UsersBase
$this->assertEquals(401, $response['headers']['status-code']);
+ // Ensure JWT 0 works still even with no sessions
+
+ $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId . '/sessions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'sessionId' => $session2Id
+ ]);
+
+ $this->assertEquals(204, $response['headers']['status-code']);
+
+ $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-jwt' => $jwt0,
+ ]));
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($userId, $response['body']['$id']);
+
// Cleanup after test
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([
diff --git a/tests/resources/functions/php-fn/index.php b/tests/resources/functions/php-fn/index.php
index b353ad2ca4..0b6e3d206c 100644
--- a/tests/resources/functions/php-fn/index.php
+++ b/tests/resources/functions/php-fn/index.php
@@ -11,6 +11,7 @@ return function ($context) {
'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 ?? '',
diff --git a/tests/resources/functions/php-large/index.php b/tests/resources/functions/php-large/index.php
index 5a9666488e..ab3d050245 100644
--- a/tests/resources/functions/php-large/index.php
+++ b/tests/resources/functions/php-large/index.php
@@ -8,6 +8,7 @@ return function ($context) {
'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' => "êä"
]);
};
diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php
index ac7b85a43a..d5328c40e1 100644
--- a/tests/resources/functions/php/index.php
+++ b/tests/resources/functions/php/index.php
@@ -8,6 +8,7 @@ return function ($context) {
'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') ?: ''
]);
diff --git a/tests/unit/Utopia/Request/Filters/V18Test.php b/tests/unit/Utopia/Request/Filters/V18Test.php
new file mode 100644
index 0000000000..4e1f81573a
--- /dev/null
+++ b/tests/unit/Utopia/Request/Filters/V18Test.php
@@ -0,0 +1,51 @@
+filter = new V18();
+ }
+
+ public function tearDown(): void
+ {
+ }
+
+ public function deleteMfaAuthenticatorProvider()
+ {
+ return [
+ 'remove otp' => [
+ [
+ 'type' => 'totp',
+ 'otp' => 1230
+ ],
+ [
+ 'type' => 'totp'
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider deleteMfaAuthenticatorProvider
+ */
+ public function testdeleteMfaAuthenticator(array $content, array $expected): void
+ {
+ $model = 'account.deleteMfaAuthenticator';
+
+ $result = $this->filter->parse($content, $model);
+
+ $this->assertEquals($expected, $result);
+ }
+}
diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php
new file mode 100644
index 0000000000..c4011c08a1
--- /dev/null
+++ b/tests/unit/Utopia/Response/Filters/V18Test.php
@@ -0,0 +1,110 @@
+filter = new V18();
+ }
+
+ public function tearDown(): void
+ {
+ }
+
+ public function functionProvider(): array
+ {
+ return [
+ 'remove scopes' => [
+ [
+ 'scopes' => [
+ 'example_scope',
+ 'example_scope2',
+ ],
+ ],
+ [
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider functionProvider
+ */
+ public function testFunction(array $content, array $expected): void
+ {
+ $model = Response::MODEL_FUNCTION;
+
+ $result = $this->filter->parse($content, $model);
+
+ $this->assertEquals($expected, $result);
+ }
+
+
+ public function executionProvider(): array
+ {
+ return [
+ 'remove scheduledAt' => [
+ [
+ 'scheduledAt' => '2024-07-13T09:00:00.000Z',
+ ],
+ [
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider executionProvider
+ */
+ public function testExecution(array $content, array $expected): void
+ {
+ $model = Response::MODEL_EXECUTION;
+
+ $result = $this->filter->parse($content, $model);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function projectProvider(): array
+ {
+ return [
+ 'remove authMockNumbers and authSessionAlerts' => [
+ [
+ 'authMockNumbers' => [
+ 'example_mock_number',
+ 'example_mock_number2',
+ ],
+ 'authSessionAlerts' => [
+ 'example_alert',
+ 'example_alert2',
+ ],
+ ],
+ [
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider projectProvider
+ */
+ public function testProject(array $content, array $expected): void
+ {
+ $model = Response::MODEL_PROJECT;
+
+ $result = $this->filter->parse($content, $model);
+
+ $this->assertEquals($expected, $result);
+ }
+}