1
0
Fork 0
mirror of synced 2024-07-03 13:41:01 +12:00

Merge pull request #6345 from appwrite/1.4.x

1.4.x
This commit is contained in:
Torsten Dittmann 2023-10-10 20:11:46 +02:00 committed by GitHub
commit 2016310907
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 729 additions and 197 deletions

2
.env
View file

@ -9,7 +9,9 @@ _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disbled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost

View file

@ -16,7 +16,7 @@ jobs:
with:
fetch-depth: 2
submodules: recursive
ref: master
ref: cl-1.4.x
- name: Login to Docker Hub
uses: docker/login-action@v2

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 3.1.1
branch = 3.2.1

View file

@ -1,3 +1,39 @@
# Version 1.4.5
## Changes
- Bump console to version 3.2.1 in [#6868](https://github.com/appwrite/appwrite/pull/6868)
## Fixes
- Fix realtime logs in [#6478](https://github.com/appwrite/appwrite/pull/6478)
- Fix "File not found" error in executor in [#6476](https://github.com/appwrite/appwrite/pull/6476)
- Fix missing array flag on migration errors response model rule in [#6469](https://github.com/appwrite/appwrite/pull/6469)
- Ensure openruntimes-executor restarts after a server reboot in [#6490](https://github.com/appwrite/appwrite/pull/6490)
# Version 1.4.4
## Features
- Feat: Function domains force https in [#6269](https://github.com/appwrite/appwrite/pull/6269)
- Feat: router protection in [#6272](https://github.com/appwrite/appwrite/pull/6272)
- Feat: Parse event body in [#6317](https://github.com/appwrite/appwrite/pull/6317)
## Fixes
- Fix: wrong device type in [#6271](https://github.com/appwrite/appwrite/pull/6271)
- Fix: build race condition in [#6270](https://github.com/appwrite/appwrite/pull/6270)
- Fix: Large builds in [#6273](https://github.com/appwrite/appwrite/pull/6273)
- Fix: migrations in [#6302](https://github.com/appwrite/appwrite/pull/6302)
- Add Description for Download Deployment in [#6268](https://github.com/appwrite/appwrite/pull/6268)
- Fix deployment delete in [#6290](https://github.com/appwrite/appwrite/pull/6290)
- Fix project deletion in [#6260](https://github.com/appwrite/appwrite/pull/6260)
- fix-6212-Issue-With-Linkedin-OAuth in [#6229](https://github.com/appwrite/appwrite/pull/6229)
- Fix: Execution body limit in [#6326](https://github.com/appwrite/appwrite/pull/6326)
- Patch: Disable console protection in [#6329](https://github.com/appwrite/appwrite/pull/6329)
- converted desc to sentence case in [#5926](https://github.com/appwrite/appwrite/pull/5926)
- Update avatar font and default colors in [#6277](https://github.com/appwrite/appwrite/pull/6277)
- Bump composer to fix migration bug in [#6344](https://github.com/appwrite/appwrite/pull/6344)
- Fix execution call timeout in [#6332](https://github.com/appwrite/appwrite/pull/6332)
- Bump appwrite-assistant to prevent it from crashing w/o open ai key in [#6342](https://github.com/appwrite/appwrite/pull/6342)
- Remove Special Chars from Initials [#6164](https://github.com/appwrite/appwrite/pull/6164)
# Version 1.4.3
## Features

View file

@ -66,7 +66,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
### Windows
@ -78,7 +78,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
#### PowerShell
@ -88,7 +88,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -76,7 +76,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
### Windows
@ -88,7 +88,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
#### PowerShell
@ -98,7 +98,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.3
appwrite/appwrite:1.4.5
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -36,13 +36,31 @@ return [
],
[
'name' => '_APP_OPTIONS_FORCE_HTTPS',
'description' => 'Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
'description' => 'Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443, and you have set up wildcard certificates with DNS challenge.',
'introduction' => '',
'default' => 'disabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS',
'description' => 'Allows you to force HTTPS connection to function domains. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
'introduction' => '',
'default' => 'disabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_OPTIONS_ROUTER_PROTECTION',
'description' => 'Protects server from serving requests from unknown hostnames, and from serving Console for custom project domains. By default, set to \'disabled\'. To start router protection, set to \'enabled\'. It is recommended to enable this variable on production environment.',
'introduction' => '1.4.4',
'default' => 'disabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_OPENSSL_KEY_V1',
'description' => 'This is your server private secret key that is used to encrypt all sensitive data on your server. Appwrite server encrypts all secret data on your server like webhooks, HTTP passwords, user sessions, and storage files. The var is not set by default, if you wish to take advantage of Appwrite encryption capabilities you should change it and make sure to **keep it a secret and have a backup for it**.',

@ -1 +1 @@
Subproject commit 9b4bcb8140484669421685b4ba89fa1c4d331360
Subproject commit 2f47e4e77b3c832679c5a83a604dd7b8e8fc1903

View file

@ -509,12 +509,11 @@ App::get('/v1/avatars/initials')
->action(function (string $name, int $width, int $height, string $background, Response $response, Document $user) {
$themes = [
['background' => '#FFA1CE'], // Default (Pink)
['background' => '#FDC584'], // Orange
['background' => '#94DBD1'], // Green
['background' => '#A1C4FF'], // Blue
['background' => '#FFA1CE'], // Pink
['background' => '#CBB1FC'] // Purple
['background' => '#FD366E'], // Default (Pink)
['background' => '#FE9567'], // Orange
['background' => '#7C67FE'], // Purple
['background' => '#68A3FE'], // Blue
['background' => '#85DBD8'], // Mint
];
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
@ -526,11 +525,13 @@ App::get('/v1/avatars/initials')
$code = 0;
foreach ($words as $key => $w) {
$initials .= $w[0] ?? '';
$code += (isset($w[0])) ? \ord($w[0]) : 0;
if (ctype_alnum($w[0] ?? '')) {
$initials .= $w[0];
$code += ord($w[0]);
if ($key == 1) {
break;
if ($key == 1) {
break;
}
}
}
@ -548,8 +549,8 @@ App::get('/v1/avatars/initials')
$punch->newImage($width, $height, 'transparent');
$draw->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
$draw->setFont(__DIR__ . "/../../assets/fonts/inter-v8-latin-regular.woff2");
$image->setFont(__DIR__ . "/../../assets/fonts/inter-v8-latin-regular.woff2");
$draw->setFillColor(new ImagickPixel('black'));
$draw->setFontSize($fontSize);
@ -724,7 +725,7 @@ App::get('/v1/cards/cloud')
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
if (\strlen($name) > 32) {
@ -1108,7 +1109,7 @@ App::get('/v1/cards/cloud-og')
$textName = new \ImagickDraw();
$textName->setTextAlignment(Imagick::ALIGN_CENTER);
$textName->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
$textName->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$textName->setFillColor(new \ImagickPixel('#FFFFFF'));
if (\strlen($name) > 32) {

View file

@ -84,6 +84,7 @@ $redeployVcs = function (Request $request, Document $function, Document $project
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'commands' => $function->getAttribute('commands', ''),
@ -1079,7 +1080,7 @@ App::post('/v1/functions/:functionId/deployments')
}
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000'));
$upload = new Upload();
// Make sure we handle a single file and multiple files the same way
@ -1495,7 +1496,7 @@ App::post('/v1/functions/:functionId/executions')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('body', '', new Text(8192, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('body', '', new Text(0, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
@ -1707,7 +1708,8 @@ App::post('/v1/functions/:functionId/executions')
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command
runtimeEntrypoint: $command,
requestTimeout: 30
);
$headersFiltered = [];

View file

@ -166,7 +166,7 @@ App::get('/v1/health/cache')
});
App::get('/v1/health/queue')
->desc('Get Queue')
->desc('Get queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
@ -223,7 +223,7 @@ App::get('/v1/health/queue')
});
App::get('/v1/health/pubsub')
->desc('Get PubSub')
->desc('Get pubsub')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
@ -387,6 +387,108 @@ App::get('/v1/health/queue/certificates')
$response->dynamic(new Document([ 'size' => Resque::size(Event::CERTIFICATES_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/builds')
->desc('Get builds queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueBuilds')
->label('sdk.description', '/docs/references/health/get-queue-builds.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::BUILDS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/databases')
->desc('Get databases queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueDatabases')
->label('sdk.description', '/docs/references/health/get-queue-databases.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::DATABASE_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/deletes')
->desc('Get deletes queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueDeletes')
->label('sdk.description', '/docs/references/health/get-queue-deletes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::DELETE_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/mails')
->desc('Get mails queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMails')
->label('sdk.description', '/docs/references/health/get-queue-mails.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::MAILS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/messaging')
->desc('Get messaging queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMessaging')
->label('sdk.description', '/docs/references/health/get-queue-messaging.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::MESSAGING_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/migrations')
->desc('Get migrations queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMigrations')
->label('sdk.description', '/docs/references/health/get-queue-migrations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::MIGRATIONS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/functions')
->desc('Get functions queue')
->groups(['api', 'health'])

View file

@ -92,16 +92,17 @@ App::post('/v1/projects')
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
$backups['database_db_fra1_02'] = ['from' => '7:30', 'to' => '8:15'];
$backups['database_db_fra1_03'] = ['from' => '10:30', 'to' => '11:15'];
$backups['database_db_fra1_04'] = ['from' => '13:30', 'to' => '14:15'];
$backups['database_db_fra1_05'] = ['from' => '4:30', 'to' => '5:15'];
$backups['database_db_fra1_06'] = ['from' => '16:30', 'to' => '17:15'];
$backups['database_db_fra1_v14x_02'] = ['from' => '7:30', 'to' => '8:15'];
$backups['database_db_fra1_v14x_03'] = ['from' => '10:30', 'to' => '11:15'];
$backups['database_db_fra1_v14x_04'] = ['from' => '13:30', 'to' => '14:15'];
$backups['database_db_fra1_v14x_05'] = ['from' => '4:30', 'to' => '5:15'];
$backups['database_db_fra1_v14x_06'] = ['from' => '16:30', 'to' => '17:15'];
$backups['database_db_fra1_v14x_07'] = ['from' => '19:30', 'to' => '20:15'];
$databases = Config::getParam('pools-database', []);
/**
* Extract db from list while backing
* Remove databases from the list that are currently undergoing an backup
*/
if (count($databases) > 1) {
$now = new \DateTime();
@ -120,7 +121,9 @@ App::post('/v1/projects')
}
}
if ($index = array_search('database_db_fra1_06', $databases)) {
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE', null);
$index = array_search($databaseOverride, $databases);
if ($index) {
$database = $databases[$index];
} else {
$database = $databases[array_rand($databases)];

View file

@ -50,6 +50,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$functionId = $resource->getAttribute('resourceId');
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$functionInternalId = $function->getInternalId();
$deploymentId = ID::unique();
$repositoryId = $resource->getId();
@ -173,6 +174,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
Permission::delete(Role::any()),
],
'resourceId' => $functionId,
'resourceInternalId' => $functionInternalId,
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands'),

View file

@ -47,6 +47,8 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleRequest, Request $request, Response $response)
{
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
$host = $request->getHostname() ?? '';
$route = Authorization::skip(
@ -57,12 +59,25 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
)[0] ?? null;
if ($route === null) {
if ($host === App::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.');
}
if (\str_ends_with($host, App::getEnv('_APP_DOMAIN_FUNCTIONS', ''))) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain is not connected to any Appwrite resource yet. Please configure custom domain or function domain to allow this request.');
}
if (App::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.');
}
}
// Act as API - no Proxy logic
$utopia->getRoute()?->label('error', '');
return false;
}
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
$projectId = $route->getAttribute('projectId');
$project = Authorization::skip(
fn () => $dbForConsole->getDocument('projects', $projectId)
@ -83,6 +98,16 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
$type = $route->getAttribute('resourceType');
if ($type === 'function') {
if (App::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
}
}
$functionId = $route->getAttribute('resourceId');
$projectId = $route->getAttribute('projectId');
@ -114,6 +139,7 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// \curl_setopt($ch, CURLOPT_HEADER, true);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$executionResponse = \curl_exec($ch);
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -164,6 +190,7 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);
}
$utopia->getRoute()?->label('error', '');
return false;
}
@ -380,7 +407,7 @@ App::init()
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP.');
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());

View file

@ -1,6 +1,5 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;

View file

@ -108,8 +108,8 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 510;
const APP_VERSION_STABLE = '1.4.3';
const APP_CACHE_BUSTER = 512;
const APP_VERSION_STABLE = '1.4.5';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';

View file

@ -84,7 +84,9 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
@ -196,6 +198,7 @@ services:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@ -381,7 +384,9 @@ services:
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
@ -648,7 +653,7 @@ services:
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.2.1
image: appwrite/assistant:0.2.2
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
@ -661,8 +666,9 @@ services:
container_name: openruntimes-executor
hostname: appwrite-executor
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.4.1
image: openruntimes/executor:0.4.3
networks:
- appwrite
- runtimes

View file

@ -112,6 +112,8 @@ class BuildsV1 extends Worker
$isNewBuild = empty($buildId);
$deviceFunctions = $this->getFunctionsDevice($project->getId());
if ($isNewBuild) {
$buildId = ID::unique();
$build = $dbForProject->createDocument('builds', new Document([
@ -124,7 +126,7 @@ class BuildsV1 extends Worker
'path' => '',
'runtime' => $function->getAttribute('runtime'),
'source' => $deployment->getAttribute('path', ''),
'sourceType' => strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)),
'sourceType' => strtolower($deviceFunctions->getType()),
'logs' => '',
'endTime' => null,
'duration' => 0,
@ -251,17 +253,24 @@ class BuildsV1 extends Worker
$tmpPath = '/tmp/builds/' . \escapeshellcmd($buildId);
$tmpPathFile = $tmpPath . '/code.tar.gz';
$localDevice = new Local();
if (substr($tmpDirectory, -1) !== '/') {
$tmpDirectory .= '/';
}
$directorySize = $localDevice->getDirectorySize($tmpDirectory);
$functionsSizeLimit = (int) App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000');
if ($directorySize > $functionsSizeLimit) {
throw new Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.');
}
Console::execute('tar --exclude code.tar.gz -czf ' . $tmpPathFile . ' -C /tmp/builds/' . \escapeshellcmd($buildId) . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr);
$deviceFunctions = $this->getFunctionsDevice($project->getId());
$localDevice = new Local();
$buffer = $localDevice->read($tmpPathFile);
$mimeType = $localDevice->getFileMimeType($tmpPathFile);
$path = $deviceFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $deviceFunctions->write($path, $buffer, $mimeType);
$result = $localDevice->transfer($tmpPathFile, $path, $deviceFunctions);
if (!$result) {
throw new \Exception("Unable to move file");
@ -480,7 +489,7 @@ class BuildsV1 extends Worker
* Send realtime Event
*/
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $build,
project: $project

View file

@ -14,6 +14,7 @@ use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\CLI\Console;
use Utopia\Audit\Audit;
use Utopia\Database\DateTime;
use Utopia\Storage\Device;
require_once __DIR__ . '/../init.php';
@ -71,7 +72,7 @@ class DeletesV1 extends Worker
$this->deleteInstallation($document, $project);
break;
case DELETE_TYPE_RULES:
$this->deleteRule($document, $project);
$this->deleteRule($document);
break;
default:
if (\str_starts_with($document->getCollection(), 'database_')) {
@ -366,17 +367,8 @@ class DeletesV1 extends Worker
$projectId = $document->getId();
$projectInternalId = $document->getInternalId();
// Delete project certificates
$dbForConsole = $this->getConsoleDB();
$domains = $dbForConsole->find('domains', [
Query::equal('projectInternalId', [$projectInternalId])
]);
foreach ($domains as $domain) {
$this->deleteCertificates($domain);
}
// Delete project tables
$dbForProject = $this->getProjectDB($document);
@ -397,10 +389,12 @@ class DeletesV1 extends Worker
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete Domains
$this->deleteByGroup('domains', [
// Delete project and function rules
$this->deleteByGroup('rules', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
], $dbForConsole, function (Document $document) {
$this->deleteRule($document);
});
// Delete Keys
$this->deleteByGroup('keys', [
@ -620,33 +614,25 @@ class DeletesV1 extends Worker
* Delete Deployments
*/
Console::info("Deleting deployments for function " . $functionId);
$storageFunctions = $this->getFunctionsDevice($projectId);
$deviceFunctions = $this->getFunctionsDevice($projectId);
$deploymentInternalIds = [];
$this->deleteByGroup('deployments', [
Query::equal('resourceInternalId', [$functionInternalId])
], $dbForProject, function (Document $document) use ($storageFunctions, &$deploymentInternalIds) {
], $dbForProject, function (Document $document) use ($deviceFunctions, &$deploymentInternalIds) {
$deploymentInternalIds[] = $document->getInternalId();
if ($storageFunctions->delete($document->getAttribute('path', ''), true)) {
Console::success('Deleted deployment files: ' . $document->getAttribute('path', ''));
} else {
Console::error('Failed to delete deployment files: ' . $document->getAttribute('path', ''));
}
$this->deleteDeploymentFiles($deviceFunctions, $document);
});
/**
* Delete builds
*/
Console::info("Deleting builds for function " . $functionId);
$storageBuilds = $this->getBuildsDevice($projectId);
$deviceBuilds = $this->getBuildsDevice($projectId);
foreach ($deploymentInternalIds as $deploymentInternalId) {
$this->deleteByGroup('builds', [
Query::equal('deploymentInternalId', [$deploymentInternalId])
], $dbForProject, function (Document $document) use ($storageBuilds) {
if ($storageBuilds->delete($document->getAttribute('path', ''), true)) {
Console::success('Deleted build files: ' . $document->getAttribute('path', ''));
} else {
Console::error('Failed to delete build files: ' . $document->getAttribute('path', ''));
}
], $dbForProject, function (Document $document) use ($deviceBuilds) {
$this->deleteBuildFiles($deviceBuilds, $document);
});
}
@ -665,6 +651,58 @@ class DeletesV1 extends Worker
$this->deleteRuntimes($document, $project);
}
protected function deleteDeploymentFiles(Device $device, Document $deployment)
{
$deploymentId = $deployment->getId();
$deploymentPath = $deployment->getAttribute('path', '');
if (empty($deploymentPath)) {
Console::info("No deployment files for deployment " . $deploymentId);
return;
}
Console::info("Deleting deployment files for deployment " . $deploymentId);
try {
if ($device->delete($deploymentPath, true)) {
Console::success('Deleted deployment files: ' . $deploymentPath);
} else {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
}
} catch (\Throwable $th) {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
protected function deleteBuildFiles(Device $device, Document $build)
{
$buildId = $build->getId();
$buildPath = $build->getAttribute('path', '');
if (empty($buildPath)) {
Console::info("No build files for build " . $buildId);
return;
}
try {
if ($device->delete($buildPath, true)) {
Console::success('Deleted build files: ' . $buildPath);
} else {
Console::error('Failed to delete build files: ' . $buildPath);
}
} catch (\Throwable $th) {
Console::error('Failed to delete deployment files: ' . $buildPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
/**
* @param Document $document deployment document
* @param Document $project
@ -679,27 +717,18 @@ class DeletesV1 extends Worker
/**
* Delete deployment files
*/
Console::info("Deleting deployment files for deployment " . $deploymentId);
$storageFunctions = $this->getFunctionsDevice($projectId);
if ($storageFunctions->delete($document->getAttribute('path', ''), true)) {
Console::success('Deleted deployment files: ' . $document->getAttribute('path', ''));
} else {
Console::error('Failed to delete deployment files: ' . $document->getAttribute('path', ''));
}
$deviceFunctions = $this->getFunctionsDevice($projectId);
$this->deleteDeploymentFiles($deviceFunctions, $document);
/**
* Delete builds
*/
Console::info("Deleting builds for deployment " . $deploymentId);
$storageBuilds = $this->getBuildsDevice($projectId);
$deviceBuilds = $this->getBuildsDevice($projectId);
$this->deleteByGroup('builds', [
Query::equal('deploymentInternalId', [$deploymentInternalId])
], $dbForProject, function (Document $document) use ($storageBuilds) {
if ($storageBuilds->delete($document->getAttribute('path', ''), true)) {
Console::success('Deleted build files: ' . $document->getAttribute('path', ''));
} else {
Console::error('Failed to delete build files: ' . $document->getAttribute('path', ''));
}
], $dbForProject, function (Document $document) use ($deviceBuilds) {
$this->deleteBuildFiles($deviceBuilds, $document);
});
@ -861,7 +890,7 @@ class DeletesV1 extends Worker
* @param Document $document rule document
* @param Document $project project document
*/
protected function deleteRule(Document $document, Document $project): void
protected function deleteRule(Document $document): void
{
$consoleDB = $this->getConsoleDB();

View file

@ -363,7 +363,8 @@ $server->job()
path: '/',
method: 'POST',
headers: [
'user-agent' => 'Appwrite/' . APP_VERSION_STABLE
'user-agent' => 'Appwrite/' . APP_VERSION_STABLE,
'content-type' => 'application/json'
],
);
Console::success('Triggered function: ' . $events[0]);

View file

@ -64,7 +64,7 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.5.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.14.*",
"utopia-php/storage": "0.17.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/vcs": "0.5.*",
"utopia-php/websocket": "0.1.*",

20
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "54d54b76790d03a0bcfb9bbd23ed1009",
"content-hash": "13a3bdc7c1dec5756bf58ec73a49753d",
"packages": [
{
"name": "adhocore/jwt",
@ -2988,16 +2988,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.14.0",
"version": "0.17.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "eda6651ac16884dc2a79ecb984ea591ba1ed498c"
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/eda6651ac16884dc2a79ecb984ea591ba1ed498c",
"reference": "eda6651ac16884dc2a79ecb984ea591ba1ed498c",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/efec5376c02d3d8330f1beb1469e6d6e313e21ee",
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee",
"shasum": ""
},
"require": {
@ -3005,10 +3005,12 @@
"ext-fileinfo": "*",
"ext-lz4": "*",
"ext-snappy": "*",
"ext-xz": "*",
"ext-zlib": "*",
"ext-zstd": "*",
"php": ">=8.0",
"utopia-php/framework": "0.*.*"
"utopia-php/framework": "0.*.*",
"utopia-php/system": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -3035,9 +3037,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.14.0"
"source": "https://github.com/utopia-php/storage/tree/0.17.0"
},
"time": "2023-03-15T00:16:34+00:00"
"time": "2023-08-21T11:28:36+00:00"
},
{
"name": "utopia-php/swoole",
@ -6017,5 +6019,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View file

@ -105,7 +105,9 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
@ -222,6 +224,7 @@ services:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@ -416,7 +419,9 @@ services:
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
@ -705,7 +710,7 @@ services:
appwrite-assistant:
container_name: appwrite-assistant
image: appwrite/assistant:0.2.1
image: appwrite/assistant:0.2.2
networks:
- appwrite
environment:
@ -716,7 +721,8 @@ services:
hostname: appwrite-executor
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.4.1
image: openruntimes/executor:0.4.3
restart: unless-stopped
networks:
- appwrite
- runtimes

View file

@ -0,0 +1 @@
Get a Deployment's contents by its unique ID. This endpoint supports range requests for partial or streaming file download.

View file

@ -0,0 +1 @@
Get the number of builds that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the number of database changes that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the number of background destructive changes that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the number of mails that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the number of messages that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the number of migrations that are waiting to be processed in the Appwrite internal queue server.

View file

@ -68,6 +68,8 @@ abstract class Migration
'1.4.1' => 'V19',
'1.4.2' => 'V19',
'1.4.3' => 'V19',
'1.4.4' => 'V19',
'1.4.5' => 'V19',
];
/**

View file

@ -93,6 +93,12 @@ class Doctor extends Action
Console::log('🟢 HTTPS force option is enabled');
}
if ('enabled' !== App::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled')) {
Console::log('🔴 HTTPS force option is disabled for function domains');
} else {
Console::log('🟢 HTTPS force option is enabled for function domains');
}
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');

View file

@ -97,6 +97,32 @@ class Hamster extends Action
/** Get Project Name */
$statsPerProject['project_name'] = $project->getAttribute('name');
/** Total Project Variables */
$statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT);
/** Total Migrations */
$statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT);
/** Get Custom SMTP */
$smtp = $project->getAttribute('smtp', null);
if ($smtp) {
$statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled';
/** Get Custom Templates Count */
$templates = array_keys($project->getAttribute('templates', []));
$statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) {
return str_contains($template, 'email');
});
$statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) {
return str_contains($template, 'sms');
});
}
/** Get total relationship attributes */
$statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [
Query::equal('type', ['relationship'])
], APP_LIMIT_COUNT);
/** Get Total Functions */
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
@ -108,6 +134,17 @@ class Hamster extends Action
/** Get Total Deployments */
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
$statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [
Query::equal('type', ['manual'])
], APP_LIMIT_COUNT);
$statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [
Query::equal('type', ['vcs'])
], APP_LIMIT_COUNT);
/** Get VCS repos connected */
$statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()])
], APP_LIMIT_COUNT);
/** Get Total Teams */
$statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT);
@ -132,19 +169,16 @@ class Hamster extends Action
throw new Exception('Membership not found. Skipping project : ' . $project->getId());
}
$userInternalId = $membership->getAttribute('userInternalId', null);
if ($userInternalId) {
$user = $dbForConsole->findOne('users', [
Query::equal('_id', [$userInternalId]),
]);
$userId = $membership->getAttribute('userId', null);
if ($userId) {
$user = $dbForConsole->getDocument('users', $userId);
$statsPerProject['email'] = $user->getAttribute('email', null);
$statsPerProject['name'] = $user->getAttribute('name', null);
}
}
/** Get Domains */
$statsPerProject['custom_domains'] = $dbForConsole->count('domains', [
$statsPerProject['custom_domains'] = $dbForConsole->count('rules', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(APP_LIMIT_COUNT)
]);
@ -234,15 +268,16 @@ class Hamster extends Action
if (!$res) {
Console::error('Failed to create user profile for project: ' . $project->getId());
}
}
$event = new Event();
$event
->setName('Project Daily Usage')
->setProps($statsPerProject);
$res = $this->mixpanel->createEvent($event);
if (!$res) {
Console::error('Failed to create event for project: ' . $project->getId());
}
$event = new Event();
$event
->setName('Project Daily Usage')
->setProps($statsPerProject);
$res = $this->mixpanel->createEvent($event);
if (!$res) {
Console::error('Failed to create event for project: ' . $project->getId());
}
} catch (Exception $e) {
Console::error('Failed to send stats for project: ' . $project->getId());
@ -362,12 +397,9 @@ class Hamster extends Action
throw new Exception('Membership not found. Skipping organization : ' . $document->getId());
}
$userInternalId = $membership->getAttribute('userInternalId', null);
if ($userInternalId) {
$user = $dbForConsole->findOne('users', [
Query::equal('_id', [$userInternalId]),
]);
$userId = $membership->getAttribute('userId', null);
if ($userId) {
$user = $dbForConsole->getDocument('users', $userId);
$statsPerOrganization['email'] = $user->getAttribute('email', null);
}

View file

@ -68,6 +68,7 @@ class Migration extends Model
->addRule('errors', [
'type' => self::TYPE_STRING,
'description' => 'All errors that occurred during the migration process.',
'array' => true,
'default' => [],
'example' => [],
])

View file

@ -70,7 +70,7 @@ class Executor
array $variables = [],
string $command = null,
) {
$runtimeId = "$projectId-$deploymentId";
$runtimeId = "$projectId-$deploymentId-build";
$route = "/runtimes";
$params = [
'runtimeId' => $runtimeId,
@ -113,7 +113,7 @@ class Executor
) {
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$runtimeId = "$projectId-$deploymentId";
$runtimeId = "$projectId-$deploymentId-build";
$route = "/runtimes/{$runtimeId}/logs";
$params = [
'timeout' => $timeout
@ -177,6 +177,7 @@ class Executor
string $method,
array $headers,
string $runtimeEntrypoint = null,
int $requestTimeout = null
) {
if (empty($headers['host'])) {
$headers['host'] = App::getEnv('_APP_DOMAIN', '');
@ -202,9 +203,13 @@ class Executor
'runtimeEntrypoint' => $runtimeEntrypoint,
];
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
// Safety timeout. Executor has timeout, and open runtime has soft timeout.
// This one shouldn't really happen, but prevents from unexpected networking behaviours.
if ($requestTimeout == null) {
$requestTimeout = $timeout + 15;
}
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {

View file

@ -202,7 +202,7 @@ trait AvatarsBase
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'https://appwrite.io/images/apple.png',
'url' => 'https://appwrite.io/images/open-graph/website.png',
]);
$this->assertEquals(200, $response['headers']['status-code']);
@ -212,7 +212,7 @@ trait AvatarsBase
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'https://appwrite.io/images/apple.png',
'url' => 'https://appwrite.io/images/open-graph/website.png',
'width' => 200,
'height' => 200,
]);
@ -224,7 +224,7 @@ trait AvatarsBase
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'https://appwrite.io/images/apple.png',
'url' => 'https://appwrite.io/images/open-graph/website.png',
'width' => 300,
'height' => 300,
'quality' => 30,
@ -251,7 +251,7 @@ trait AvatarsBase
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'https://appwrite.io/images/apple.png',
'url' => 'https://appwrite.io/images/open-graph/website.png',
'width' => 2001,
'height' => 300,
'quality' => 30,
@ -280,11 +280,11 @@ trait AvatarsBase
$response = $this->client->call(Client::METHOD_GET, '/avatars/favicon', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'https://appwrite.io/',
'url' => 'https://github.com/',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('image/png', $response['headers']['content-type']);
$this->assertEquals('image/x-icon', $response['headers']['content-type']);
$this->assertNotEmpty($response['body']);
/**
@ -524,4 +524,28 @@ trait AvatarsBase
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/initials.png')), strlen($response['body']));
}
public function testSpecialCharsInitalImage()
{
$response = $this->client->call(Client::METHOD_GET, '/avatars/initials', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'name' => 'W (Hello) W',
'width' => 200,
'height' => 200,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('image/png', $response['headers']['content-type']);
$this->assertNotEmpty($response['body']);
$image = new \Imagick();
$image->readImageBlob($response['body']);
$original = new \Imagick(__DIR__ . '/../../../resources/initials.png');
$this->assertEquals($image->getImageWidth(), $original->getImageWidth());
$this->assertEquals($image->getImageHeight(), $original->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/initials.png')), strlen($response['body']));
}
}

View file

@ -32,8 +32,8 @@ class FunctionsCustomServerTest extends Scope
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [
'users.*.create',
'users.*.delete',
'buckets.*.create',
'buckets.*.delete',
],
'schedule' => '0 0 1 1 *',
'timeout' => 10,
@ -50,8 +50,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt']));
$this->assertEquals('', $response1['body']['deployment']);
$this->assertEquals([
'users.*.create',
'users.*.delete',
'buckets.*.create',
'buckets.*.delete',
], $response1['body']['events']);
$this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
$this->assertEquals(10, $response1['body']['timeout']);
@ -191,8 +191,8 @@ class FunctionsCustomServerTest extends Scope
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [
'users.*.create',
'users.*.delete',
'buckets.*.create',
'buckets.*.delete',
],
'schedule' => '0 0 1 1 *',
'timeout' => 10,
@ -1231,4 +1231,117 @@ class FunctionsCustomServerTest extends Scope
$this->assertArrayHasKey('base', $runtime);
$this->assertArrayHasKey('supports', $runtime);
}
public function testEventTrigger()
{
$timeout = 5;
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-event/code.tar.gz";
$this->packageCode('php-event');
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test PHP Event executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [
'users.*.create',
],
'timeout' => $timeout,
]);
$functionId = $function['body']['$id'] ?? '';
$this->assertEquals(201, $function['headers']['status-code']);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
'activate' => true
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
// Poll until deployment is built
while (true) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $deployment['headers']['status-code']);
// Wait a little for activation to finish
sleep(5);
// Create user to trigger event
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'name' => 'Event User'
]);
$userId = $user['body']['$id'];
$this->assertEquals(201, $user['headers']['status-code']);
// Wait for execution to occur
sleep(15);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$execution = $executions['body']['executions'][0];
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $execution['status']);
$this->assertEquals(204, $execution['responseStatusCode']);
$this->assertStringContainsString($userId, $execution['logs']);
$this->assertStringContainsString('Event User', $execution['logs']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
// Cleanup : Delete user
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
}

View file

@ -155,7 +155,7 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(4959, \strlen($initials['body']));
$this->assertEquals(5041, \strlen($initials['body']));
return $initials['body'];
}

View file

@ -29,10 +29,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['ping']);
$this->assertLessThan(100, $response['body']['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -51,10 +47,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['statuses'][0]['ping']);
$this->assertLessThan(100, $response['body']['statuses'][0]['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -73,10 +65,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['statuses'][0]['ping']);
$this->assertLessThan(100, $response['body']['statuses'][0]['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -95,10 +83,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['statuses'][0]['ping']);
$this->assertLessThan(100, $response['body']['statuses'][0]['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -117,10 +101,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['statuses'][0]['ping']);
$this->assertLessThan(100, $response['body']['statuses'][0]['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -141,10 +121,6 @@ class HealthCustomServerTest extends Scope
$this->assertNotEmpty($response['body']['localTime']);
$this->assertLessThan(10, $response['body']['diff']);
/**
* Test for FAILURE
*/
return [];
}
@ -162,10 +138,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
/**
* Test for FAILURE
*/
return [];
}
@ -183,10 +155,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
/**
* Test for FAILURE
*/
return [];
}
@ -204,9 +172,124 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testFunctionsSuccess(): array
{
/**
* Test for FAILURE
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testBuildsSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/builds', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testDatabasesSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testDeletesSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/deletes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testMailsSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/mails', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testMessagingSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/messaging', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
public function testMigrationsSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/migrations', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(100, $response['body']['size']);
return [];
}
@ -226,10 +309,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsInt($response['body']['ping']);
$this->assertLessThan(100, $response['body']['ping']);
/**
* Test for FAILURE
*/
return [];
}
@ -248,10 +327,6 @@ class HealthCustomServerTest extends Scope
$this->assertIsString($response['body']['status']);
$this->assertIsString($response['body']['version']);
/**
* Test for FAILURE
*/
return [];
}
}

View file

@ -595,6 +595,18 @@ trait UsersBase
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => '>',
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertEmpty($response['body']['users']);
$this->assertCount(0, $response['body']['users']);
/**
* Test for FAILURE
*/

View file

@ -66,7 +66,9 @@ services:
environment:
- _APP_ENV
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_FUNCTIONS

View file

@ -0,0 +1,8 @@
<?php
return function ($context) {
$context->log($context->req->body['$id']);
$context->log($context->req->body['name']);
return $context->res->empty();
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB