Merge branch '1.5.x' into feat-migration

# Conflicts:
#	composer.json
#	composer.lock
This commit is contained in:
Jake Barnby 2024-08-05 21:13:42 +12:00
commit e2a45cec8e
No known key found for this signature in database
GPG key ID: C437A8CC85B96E9C
64 changed files with 55265 additions and 181 deletions

3
.env
View file

@ -9,7 +9,8 @@ _APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
_APP_EMAIL_SECURITY=security@appwrite.io
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disabled

2
.gitmodules vendored
View file

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

View file

@ -1,3 +1,37 @@
# Version 1.5.8
## What's Changed
### Notable changes
* Support Twilio messaging service SID in [8222](https://github.com/appwrite/appwrite/pull/8222)
* Improve cache performance in [8230](https://github.com/appwrite/appwrite/pull/8230)
* Add hk in translations in [8179](https://github.com/appwrite/appwrite/pull/8179)
* Bump console to version 4.3.14 in [8321](https://github.com/appwrite/appwrite/pull/8321)
* Update pwd abuse in [8255](https://github.com/appwrite/appwrite/pull/8255)
### Fixes
* Ensure usage is counted for errors in [8120](https://github.com/appwrite/appwrite/pull/8120)
* Fix MFA for OAuth2 only accounts in [8245](https://github.com/appwrite/appwrite/pull/8245)
* Delete Expired Targets Per Project in [8239](https://github.com/appwrite/appwrite/pull/8239)
* Don't set the target field if the existing target document is false in [8236](https://github.com/appwrite/appwrite/pull/8236)
* Disable validation for project DBs during migration in [8298](https://github.com/appwrite/appwrite/pull/8298)
* Add `default` to Collection Attributes in Migration in [8271](https://github.com/appwrite/appwrite/pull/8271)
* Fix Create bucket endpoint validator for maximum file size in [8275](https://github.com/appwrite/appwrite/pull/8275)
* Disable validation for subquery to prevent error in [8297](https://github.com/appwrite/appwrite/pull/8297)
* Fix 'Missing required attribute "expire"' on `users.createSession()` in [8308](https://github.com/appwrite/appwrite/pull/8308)
### Miscellaneous
* Bump base image from 0.9.0 to 0.9.1 in [8238](https://github.com/appwrite/appwrite/pull/8238)
* Use latest Platform and add Core module in [7936](https://github.com/appwrite/appwrite/pull/7936)
* Add Test to Validate Headers aren't Overridden in [8228](https://github.com/appwrite/appwrite/pull/8228)
* Fix hyperlink in storage docs in [8269](https://github.com/appwrite/appwrite/pull/8269)
* Update cache & database in [8285](https://github.com/appwrite/appwrite/pull/8285)
* Fix flaky certificate test in [8316](https://github.com/appwrite/appwrite/pull/8316)
* Fix flaky function test in [8317](https://github.com/appwrite/appwrite/pull/8317)
# Version 1.5.7
## What's Changed

View file

@ -67,7 +67,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.5.7
appwrite/appwrite:1.5.8
```
### Windows
@ -79,7 +79,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.5.7
appwrite/appwrite:1.5.8
```
#### PowerShell
@ -89,7 +89,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.5.7
appwrite/appwrite:1.5.8
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -75,7 +75,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.5.7
appwrite/appwrite:1.5.8
```
### Windows
@ -87,7 +87,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.5.7
appwrite/appwrite:1.5.8
```
#### PowerShell
@ -97,7 +97,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.5.7
appwrite/appwrite:1.5.8
```
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.

View file

@ -185,7 +185,6 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);

View file

@ -4,7 +4,9 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">{{error}}</p>
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">
{{error}}
</p>
</td>
</tr>
</table>

View file

@ -43,6 +43,12 @@
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
"emails.invitation.thanks": "Thanks",
"emails.invitation.signature": "{{project}} team",
"emails.certificate.subject": "Certificate failure for %s",
"emails.certificate.hello": "Hello",
"emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}",
"emails.certificate.footer": "Your previous certificate will be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.",
"emails.certificate.thanks": "Thanks",
"emails.certificate.signature": "{{project}} team",
"sms.verification.body": "{{secret}}",
"locale.country.unknown": "Unknown",
"countries.af": "Afghanistan",

View file

@ -375,7 +375,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.8.2',
'version' => '0.8.3',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,

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

@ -162,13 +162,31 @@ return [
],
[
'name' => '_APP_SYSTEM_SECURITY_EMAIL_ADDRESS',
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload.',
'description' => 'Deprecated since 1.5.1 use _APP_EMAIL_SECURITY and _APP_EMAIL_CERTIFICATES instead',
'introduction' => '0.7.0',
'default' => 'certs@appwrite.io',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_EMAIL_SECURITY',
'description' => 'This is the email address used as the user agent in your webhooks payload.',
'introduction' => '1.5.1',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_EMAIL_CERTIFICATES',
'description' => 'This is the email address used to issue SSL certificates for custom domains',
'introduction' => '1.5.1',
'default' => '',
'required' => true,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_USAGE_STATS',
'description' => 'This variable allows you to disable the collection and displaying of usage stats. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'. When disabled, it\'s recommended to turn off the Worker Usage container to reduce resource usage.',

@ -1 +1 @@
Subproject commit 5169fe16d63066f64ab5013c78953aea04e24b53
Subproject commit 112fccf5a044c9795b31b264f5974224a3545a17

View file

@ -1672,10 +1672,10 @@ App::post('/v1/account/tokens/magic-url')
->inject('queueForEvents')
->inject('queueForMails')
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
$url = htmlentities($url);
if ($phrase === true) {
$phrase = Phrase::generate();
@ -2860,6 +2860,7 @@ App::post('/v1/account/recovery')
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
@ -3123,6 +3124,7 @@ App::post('/v1/account/verification')
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
if ($user->getAttribute('emailVerification')) {
throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED);
}
@ -3429,7 +3431,7 @@ App::post('/v1/account/verification/phone')
});
App::put('/v1/account/verification/phone')
->desc('Create phone verification (confirmation)')
->desc('Update phone verification (confirmation)')
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].verification.[tokenId].update')
@ -3572,7 +3574,7 @@ App::get('/v1/account/mfa/factors')
});
App::post('/v1/account/mfa/authenticators/:type')
->desc('Add Authenticator')
->desc('Create Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@ -3882,7 +3884,7 @@ App::delete('/v1/account/mfa/authenticators/:type')
});
App::post('/v1/account/mfa/challenge')
->desc('Create 2FA Challenge')
->desc('Create MFA Challenge')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('event', 'users.[userId].challenges.[challengeId].create')

View file

@ -321,7 +321,7 @@ App::get('/v1/avatars/favicon')
->setUserAgent(\sprintf(
APP_USERAGENT,
System::getEnv('_APP_VERSION', 'UNKNOWN'),
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
))
->fetch($url);
} catch (\Throwable) {

View file

@ -2944,17 +2944,26 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$processDocument($collection, $document);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
;
->setPayload($response->getPayload(), sensitive: $relationships);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@ -3525,15 +3534,23 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$processDocument($collection, $document);
$response->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
;
$response->dynamic($document, Response::MODEL_DOCUMENT);
->setPayload($response->getPayload(), sensitive: $relationships);
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
@ -3629,6 +3646,14 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$processDocument($collection, $document);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForDeletes
->setType(DELETE_TYPE_AUDIT)
->setDocument($document);
@ -3639,7 +3664,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->output($document, Response::MODEL_DOCUMENT));
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
$response->noContent();
});

View file

@ -499,6 +499,8 @@ App::get('/v1/functions/:functionId/usage')
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@ -561,6 +563,10 @@ App::get('/v1/functions/:functionId/usage')
'buildsTime' => $usage[$metrics[4]]['data'],
'executions' => $usage[$metrics[5]]['data'],
'executionsTime' => $usage[$metrics[6]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
'buildsMbSeconds' => $usage[$metrics[7]]['data'],
'executionsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[8]]['total']
]), Response::MODEL_USAGE_FUNCTION);
});
@ -591,6 +597,8 @@ App::get('/v1/functions/usage')
METRIC_BUILDS_COMPUTE,
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_COMPUTE,
METRIC_BUILDS_MB_SECONDS,
METRIC_EXECUTIONS_MB_SECONDS,
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@ -654,6 +662,10 @@ App::get('/v1/functions/usage')
'buildsTime' => $usage[$metrics[5]]['data'],
'executions' => $usage[$metrics[6]]['data'],
'executionsTime' => $usage[$metrics[7]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
'buildsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSeconds' => $usage[$metrics[9]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[9]]['total'],
]), Response::MODEL_USAGE_FUNCTIONS);
});
@ -838,7 +850,7 @@ App::put('/v1/functions/:functionId')
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->groups(['api', 'functions'])
->desc('Download Deployment')
->desc('Download deployment')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
@ -923,7 +935,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
->groups(['api', 'functions'])
->desc('Update function deployment')
->desc('Update deployment')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
@ -1761,6 +1773,8 @@ App::post('/v1/functions/:functionId/executions')
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0)))
;
if ($function->getAttribute('logging')) {

View file

@ -40,18 +40,23 @@ App::get('/v1/project/usage')
$metrics = [
'total' => [
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DOCUMENTS,
METRIC_DATABASES,
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE
METRIC_FILES_STORAGE,
METRIC_DEPLOYMENTS_STORAGE
],
'period' => [
METRIC_NETWORK_REQUESTS,
METRIC_NETWORK_INBOUND,
METRIC_NETWORK_OUTBOUND,
METRIC_USERS,
METRIC_EXECUTIONS
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS
]
];
@ -144,6 +149,54 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('buckets'));
$deploymentsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
// merge network inbound + outbound
$projectBandwidth = [];
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
@ -171,13 +224,19 @@ App::get('/v1/project/usage')
'users' => ($usage[METRIC_USERS]),
'executions' => ($usage[METRIC_EXECUTIONS]),
'executionsTotal' => $total[METRIC_EXECUTIONS],
'executionsMbSecondsTotal' => $total[METRIC_EXECUTIONS_MB_SECONDS],
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'databasesTotal' => $total[METRIC_DATABASES],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'executionsBreakdown' => $executionsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
'deploymentsStorageBreakdown' => $deploymentsStorageBreakdown,
]), Response::MODEL_USAGE_PROJECT);
});

View file

@ -56,6 +56,7 @@ App::init()
App::post('/v1/projects')
->desc('Create project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.create')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@ -825,6 +826,7 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
App::delete('/v1/projects/:projectId')
->desc('Delete project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.delete')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@ -1321,6 +1323,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
App::post('/v1/projects/:projectId/platforms')
->desc('Create platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.create')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@ -1484,6 +1487,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
App::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.delete')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')

View file

@ -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)

View file

@ -387,7 +387,7 @@ App::post('/v1/teams/:teamId/memberships')
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')
@ -401,6 +401,7 @@ App::post('/v1/teams/:teamId/memberships')
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$url = htmlentities($url);
if (empty($url)) {
if (!$isAPIKey && !$isPrivilegedUser) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'URL is required');

View file

@ -1784,7 +1784,7 @@ App::post('/v1/users/:userId/sessions')
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::codeGenerator();
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@ -1801,6 +1801,7 @@ App::post('/v1/users/:userId/sessions')
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => $expire,
],
$detector->getOS(),
$detector->getClient(),
@ -1812,7 +1813,6 @@ App::post('/v1/users/:userId/sessions')
$session = $dbForProject->createDocument('sessions', $session);
$session
->setAttribute('secret', $secret)
->setAttribute('expire', $expire)
->setAttribute('countryName', $countryName);
$queueForEvents

View file

@ -310,6 +310,10 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0)))
->setProject($project)
->trigger()
;
if ($function->getAttribute('logging')) {
@ -797,7 +801,6 @@ App::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");

View file

@ -462,7 +462,12 @@ App::init()
->setContentType($cacheLog->getAttribute('mimeType'))
->send($data);
} else {
$response->addHeader('X-Appwrite-Cache', 'miss');
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Pragma', 'no-cache')
->addHeader('Expires', 0)
->addHeader('X-Appwrite-Cache', 'miss')
;
}
}
});

View file

@ -292,7 +292,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('detailedTrace', $th->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");

View file

@ -112,8 +112,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 = 443;
const APP_VERSION_STABLE = '1.5.7';
const APP_CACHE_BUSTER = 4323;
const APP_VERSION_STABLE = '1.5.8';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@ -231,15 +231,19 @@ const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
const METRIC_BUILDS = 'builds';
const METRIC_BUILDS_STORAGE = 'builds.storage';
const METRIC_BUILDS_COMPUTE = 'builds.compute';
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
const METRIC_EXECUTIONS = 'executions';
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
const METRIC_NETWORK_REQUESTS = 'network.requests';
const METRIC_NETWORK_INBOUND = 'network.inbound';
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
@ -607,9 +611,9 @@ Database::addFilter(
])
));
if (\count($targetIds) > 0) {
return $database->find('targets', [
return $database->skipValidation(fn () => $database->find('targets', [
Query::equal('$internalId', $targetIds)
]);
]));
}
return [];
}
@ -1008,7 +1012,7 @@ foreach ($locales as $locale) {
'user_agent' => \sprintf(
APP_USERAGENT,
System::getEnv('_APP_VERSION', 'UNKNOWN'),
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
),
'timeout' => 2,
],

View file

@ -178,7 +178,6 @@ $logError = function (Throwable $error, string $action) use ($register) {
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);

View file

@ -79,7 +79,7 @@ $image = $this->getParam('image', '');
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
@ -249,6 +249,7 @@ $image = $this->getParam('image', '');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_DB_HOST
- _APP_DB_PORT
@ -430,7 +431,7 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -580,7 +581,7 @@ $image = $this->getParam('image', '');
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER

View file

@ -341,7 +341,6 @@ $worker
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';

View file

@ -59,7 +59,7 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.5.*",
"utopia-php/messaging": "0.12.*",
"utopia-php/migration": "0.4.*",
"utopia-php/migration": "0.5.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.5.*",

124
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": "6e3cf9eae3ab957e66198445f7fd7381",
"content-hash": "7680daa08c49ca1a1307df557c9ee87c",
"packages": [
{
"name": "adhocore/jwt",
@ -355,16 +355,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": {
@ -372,15 +372,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/",
@ -417,7 +418,7 @@
"type": "ko_fi"
}
],
"time": "2024-01-05T23:20:55+00:00"
"time": "2024-07-17T01:04:28+00:00"
},
{
"name": "dragonmantank/cron-expression",
@ -1128,16 +1129,16 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.29.0",
"version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"shasum": ""
},
"require": {
@ -1188,7 +1189,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
},
"funding": [
{
@ -1204,7 +1205,7 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/polyfill-php80",
@ -1723,12 +1724,12 @@
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "28e41327874f849427a2b9c2f02d9d1a70d6b0cb"
"reference": "6b0defbcb442507a5928afb7c40035f245e04d5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/28e41327874f849427a2b9c2f02d9d1a70d6b0cb",
"reference": "28e41327874f849427a2b9c2f02d9d1a70d6b0cb",
"url": "https://api.github.com/repos/utopia-php/database/zipball/6b0defbcb442507a5928afb7c40035f245e04d5c",
"reference": "6b0defbcb442507a5928afb7c40035f245e04d5c",
"shasum": ""
},
"require": {
@ -1771,7 +1772,7 @@
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/feat-migrations"
},
"time": "2024-07-02T01:48:23+00:00"
"time": "2024-07-23T05:38:47+00:00"
},
{
"name": "utopia-php/domains",
@ -1921,16 +1922,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.6",
"version": "0.33.7",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6"
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
"url": "https://api.github.com/repos/utopia-php/http/zipball/78d293d99a262bd63ece750bbf989c7e0643b825",
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825",
"shasum": ""
},
"require": {
@ -1960,9 +1961,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.6"
"source": "https://github.com/utopia-php/http/tree/0.33.7"
},
"time": "2024-03-21T18:10:57+00:00"
"time": "2024-08-01T14:01:04+00:00"
},
{
"name": "utopia-php/image",
@ -2170,16 +2171,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.4.4",
"version": "0.5.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33"
"reference": "f18d44d4459f78c292dac9edde856fd156fe497a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/a8a5d392bebf082faf289f4dfe09d9fd76844c33",
"reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/f18d44d4459f78c292dac9edde856fd156fe497a",
"reference": "f18d44d4459f78c292dac9edde856fd156fe497a",
"shasum": ""
},
"require": {
@ -2189,6 +2190,7 @@
"require-dev": {
"laravel/pint": "1.*",
"phpunit/phpunit": "9.*",
"utopia-php/cli": "^0.18.0",
"vlucas/phpdotenv": "5.*"
},
"type": "library",
@ -2211,9 +2213,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.4.4"
"source": "https://github.com/utopia-php/migration/tree/0.5.2"
},
"time": "2024-05-17T05:25:31+00:00"
"time": "2024-07-22T09:27:07+00:00"
},
{
"name": "utopia-php/mongo",
@ -2988,16 +2990,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.38.7",
"version": "0.38.8",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a"
"reference": "6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a",
"reference": "0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef",
"reference": "6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef",
"shasum": ""
},
"require": {
@ -3033,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.38.7"
"source": "https://github.com/appwrite/sdk-generator/tree/0.38.8"
},
"time": "2024-06-10T00:23:02+00:00"
"time": "2024-06-17T00:42:27+00:00"
},
{
"name": "doctrine/deprecations",
@ -3156,16 +3158,16 @@
},
{
"name": "laravel/pint",
"version": "v1.16.0",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98"
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98",
"reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98",
"url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"shasum": ""
},
"require": {
@ -3176,13 +3178,13 @@
"php": "^8.1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.57.1",
"illuminate/view": "^10.48.10",
"larastan/larastan": "^2.9.6",
"friendsofphp/php-cs-fixer": "^3.59.3",
"illuminate/view": "^10.48.12",
"larastan/larastan": "^2.9.7",
"laravel-zero/framework": "^10.4.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^1.15.1",
"pestphp/pest": "^2.34.7"
"pestphp/pest": "^2.34.8"
},
"bin": [
"builds/pint"
@ -3218,7 +3220,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2024-05-21T18:08:25+00:00"
"time": "2024-08-01T09:06:33+00:00"
},
{
"name": "matthiasmullie/minify",
@ -3406,16 +3408,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.0.2",
"version": "v5.1.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
"shasum": ""
},
"require": {
@ -3426,7 +3428,7 @@
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@ -3458,9 +3460,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
},
"time": "2024-03-05T20:51:40+00:00"
"time": "2024-07-01T20:03:41+00:00"
},
{
"name": "phar-io/manifest",
@ -5340,16 +5342,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.29.0",
"version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"shasum": ""
},
"require": {
@ -5399,7 +5401,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
},
"funding": [
{
@ -5415,7 +5417,7 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-05-31T15:07:36+00:00"
},
{
"name": "textalk/websocket",

View file

@ -101,7 +101,7 @@ services:
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
@ -289,7 +289,7 @@ services:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -482,7 +482,7 @@ services:
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -646,7 +646,7 @@ services:
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER

View file

@ -86,6 +86,7 @@ abstract class Migration
'1.5.5' => 'V20',
'1.5.6' => 'V20',
'1.5.7' => 'V20',
'1.5.8' => 'V20',
];
/**
@ -239,7 +240,7 @@ abstract class Migration
}
/**
* Creates colletion from the config collection.
* Creates collection from the config collection.
*
* @param string $id
* @param string|null $name
@ -266,6 +267,7 @@ abstract class Migration
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'default' => $attribute['default'] ?? null,
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],

View file

@ -336,6 +336,32 @@ class V20 extends Migration
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
}
break;
case 'topics':
try {
$this->projectDB->updateAttributeDefault($id, 'emailTotal', 0);
} catch (Throwable $th) {
Console::warning("'topics' from {$id}: {$th->getMessage()}");
}
try {
$this->projectDB->updateAttributeDefault($id, 'pushTotal', 0);
} catch (Throwable $th) {
Console::warning("'topics' from {$id}: {$th->getMessage()}");
}
try {
$this->projectDB->updateAttributeDefault($id, 'smsTotal', 0);
} catch (Throwable $th) {
Console::warning("'topics' from {$id}: {$th->getMessage()}");
}
try {
$this->projectDB->purgeCachedCollection($id);
} catch (Throwable $th) {
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
}
break;
}

View file

@ -3,8 +3,8 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Migration\Migration;
use Redis;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -12,10 +12,13 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
use Utopia\System\System;
use Utopia\Validator\Text;
class Migrate extends Action
{
protected Redis $redis;
public static function getName(): string
{
return 'migrate';
@ -27,34 +30,54 @@ class Migrate extends Action
->desc('Migrate Appwrite to new version')
/** @TODO APP_VERSION_STABLE needs to be defined */
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
->inject('cache')
->inject('dbForConsole')
->inject('getProjectDB')
->inject('register')
->callback(fn ($version, $cache, $dbForConsole, $getProjectDB, Registry $register) => $this->action($version, $cache, $dbForConsole, $getProjectDB, $register));
->callback(fn ($version, $dbForConsole, $getProjectDB, Registry $register) => $this->action($version, $dbForConsole, $getProjectDB, $register));
}
private function clearProjectsCache(Cache $cache, Document $project)
private function clearProjectsCache(Document $project)
{
try {
$cache->purge("cache-_{$project->getInternalId()}:*");
do {
$iterator = null;
$pattern = "default-cache-_{$project->getInternalId()}:*";
$keys = $this->redis->scan($iterator, $pattern, 1000);
if ($keys !== false) {
foreach ($keys as $key) {
$this->redis->del($key);
}
}
} while ($iterator > 0);
} catch (\Throwable $th) {
Console::error('Failed to clear project ("' . $project->getId() . '") cache with error: ' . $th->getMessage());
Console::error('Failed to clear project ("'.$project->getId().'") cache with error: '.$th->getMessage());
}
}
public function action(string $version, Cache $cache, Database $dbForConsole, callable $getProjectDB, Registry $register)
public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register)
{
Authorization::disable();
if (!array_key_exists($version, Migration::$versions)) {
if (! array_key_exists($version, Migration::$versions)) {
Console::error("Version {$version} not found.");
Console::exit(1);
return;
}
$this->redis = new Redis();
$this->redis->connect(
System::getEnv('_APP_REDIS_HOST', null),
System::getEnv('_APP_REDIS_PORT', 6379),
3,
null,
10
);
$app = new App('UTC');
Console::success('Starting Data Migration to version ' . $version);
Console::success('Starting Data Migration to version '.$version);
$console = $app->getResource('console');
@ -74,11 +97,11 @@ class Migrate extends Action
$totalProjects = $dbForConsole->count('projects') + 1;
}
$class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version];
$class = 'Appwrite\\Migration\\Version\\'.Migration::$versions[$version];
/** @var Migration $migration */
$migration = new $class();
while (!empty($projects)) {
while (! empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
@ -87,22 +110,23 @@ class Migrate extends Action
continue;
}
$this->clearProjectsCache($cache, $project);
$this->clearProjectsCache($project);
try {
// TODO: Iterate through all project DBs
/** @var Database $projectDB */
$projectDB = $getProjectDB($project);
$projectDB->disableValidation();
$migration
->setProject($project, $projectDB, $dbForConsole)
->setPDO($register->get('db', true))
->execute();
} catch (\Throwable $th) {
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
Console::error('Failed to update project ("'.$project->getId().'") version with error: '.$th->getMessage());
throw $th;
}
$this->clearProjectsCache($cache, $project);
$this->clearProjectsCache($project);
}
$sum = \count($projects);
@ -111,7 +135,7 @@ class Migrate extends Action
$offset = $offset + $limit;
$count = $count + $sum;
Console::log('Migrated ' . $count . '/' . $totalProjects . ' projects...');
Console::log('Migrated '.$count.'/'.$totalProjects.' projects...');
}
Console::success('Data Migration Completed');

View file

@ -318,6 +318,9 @@ class Builds extends Action
throw new \Exception("Unable to move file");
}
$deployment->setAttribute('path', $path);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
Console::execute('rm -rf ' . $tmpPath, '', $stdout, $stderr);
$source = $path;
@ -540,9 +543,11 @@ class Builds extends Action
->addMetric(METRIC_BUILDS, 1) // per project
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(512 * $build->getAttribute('duration', 0)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1) // per function
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(512 * $build->getAttribute('duration', 0)))
->setProject($project)
->trigger();
}

View file

@ -135,9 +135,9 @@ class Certificates extends Action
try {
// Email for alerts is required by LetsEncrypt
$email = System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate.');
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue an SSL certificate.');
}
// Validate domain and DNS records. Skip if job is forced
@ -439,42 +439,26 @@ class Certificates extends Action
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
// Send mail to administratore mail
// Send mail to administrator mail
$template = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-certificate-failed.tpl');
$template->setParam('{{domain}}', $domain);
$template->setParam('{{error}}', \nl2br($errorMessage));
$template->setParam('{{attempts}}', $attempt);
// TODO: Use setbodyTemplate once #7307 is merged
$subject = 'Certificate failed to generate';
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl');
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $locale->getText("emails.certificate.body"), escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
$body = $message->render();
$body = $template->render();
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'domain' => $domain,
'error' => '<br><pre>' . $errorMessage . '</pre>',
'attempt' => $attempt,
'project' => 'Console',
'redirect' => 'https://' . $domain,
];
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
$queueForMails
->setSubject($subject)
->setBody($body)
->setName('Appwrite Administrator')
->setbodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
->setVariables($emailVariables)
->setRecipient(System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
->trigger();
}

View file

@ -507,6 +507,8 @@ class Functions extends Action
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000))// per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000))
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0)))
->trigger()
;
}

View file

@ -300,11 +300,11 @@ class Migrations extends Action
$errorMessages = [];
foreach ($sourceErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while fetching '{$error->getResourceType()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
}
foreach ($destinationErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while pushing '{$error->getResourceType()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
}
$migrationDocument->setAttribute('errors', $errorMessages);
@ -337,11 +337,11 @@ class Migrations extends Action
$errorMessages = [];
foreach ($sourceErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while fetching '{$error->getResourceType()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
}
foreach ($destinationErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while pushing '{$error->getResourceType()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
}
$migrationDocument->setAttribute('errors', $errorMessages);

View file

@ -104,7 +104,7 @@ class Webhooks extends Action
\curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(
APP_USERAGENT,
System::getEnv('_APP_VERSION', 'UNKNOWN'),
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
));
\curl_setopt(
$ch,

View file

@ -46,6 +46,12 @@ class UsageFunction extends Model
'default' => 0,
'example' => 0,
])
->addRule('buildsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated sum of function builds mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('executionsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of function executions.',
@ -58,6 +64,12 @@ class UsageFunction extends Model
'default' => 0,
'example' => 0,
])
->addRule('executionsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated sum of function executions mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('deployments', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of function deployments per period.',
@ -93,6 +105,13 @@ class UsageFunction extends Model
'example' => [],
'array' => true
])
->addRule('buildsMbSeconds', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of function builds mbSeconds per period.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('executions', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of function executions per period.',
@ -108,6 +127,13 @@ class UsageFunction extends Model
'example' => [],
'array' => true
])
->addRule('executionsMbSeconds', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of function mbSeconds per period.',
'default' => [],
'example' => [],
'array' => true
])
;
}

View file

@ -52,6 +52,12 @@ class UsageFunctions extends Model
'default' => 0,
'example' => 0,
])
->addRule('buildsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated sum of functions build mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('executionsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of functions execution.',
@ -64,6 +70,12 @@ class UsageFunctions extends Model
'default' => 0,
'example' => 0,
])
->addRule('executionsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated sum of functions execution mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('functions', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of functions per period.',
@ -106,6 +118,13 @@ class UsageFunctions extends Model
'example' => [],
'array' => true
])
->addRule('buildsMbSeconds', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated sum of functions build mbSeconds per period.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('executions', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of functions execution per period.',
@ -121,6 +140,13 @@ class UsageFunctions extends Model
'example' => [],
'array' => true
])
->addRule('executionsMbSeconds', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of functions mbSeconds per period.',
'default' => [],
'example' => [],
'array' => true
])
;
}

View file

@ -40,12 +40,30 @@ class UsageProject extends Model
'default' => 0,
'example' => 0,
])
->addRule('deploymentsStorageTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated sum of deployments storage size (in bytes).',
'default' => 0,
'example' => 0,
])
->addRule('bucketsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of buckets.',
'default' => 0,
'example' => 0,
])
->addRule('executionsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of function executions mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('buildsMbSecondsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of function builds mbSeconds.',
'default' => 0,
'example' => 0,
])
->addRule('requests', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of requests per period.',
@ -88,6 +106,27 @@ class UsageProject extends Model
'example' => [],
'array' => true
])
->addRule('executionsMbSecondsBreakdown', [
'type' => Response::MODEL_METRIC_BREAKDOWN,
'description' => 'Aggregated breakdown in totals of execution mbSeconds by functions.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('buildsMbSecondsBreakdown', [
'type' => Response::MODEL_METRIC_BREAKDOWN,
'description' => 'Aggregated breakdown in totals of build mbSeconds by functions.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('deploymentsStorageBreakdown', [
'type' => Response::MODEL_METRIC_BREAKDOWN,
'description' => 'Aggregated breakdown in totals of deployments storage size (in bytes).',
'default' => [],
'example' => [],
'array' => true
])
;
}

View file

@ -140,7 +140,7 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(12, count($response['body']));
$this->assertEquals(18, count($response['body']));
$this->validateDates($response['body']['network']);
$this->validateDates($response['body']['requests']);
$this->validateDates($response['body']['users']);
@ -321,7 +321,7 @@ class UsageTest extends Scope
]
);
$this->assertEquals(12, count($response['body']));
$this->assertEquals(18, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
$this->validateDates($response['body']['requests']);
@ -542,7 +542,7 @@ class UsageTest extends Scope
]
);
$this->assertEquals(12, count($response['body']));
$this->assertEquals(18, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals(1, count($response['body']['network']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
@ -774,14 +774,19 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(15, count($response['body']));
$this->assertEquals(19, count($response['body']));
$this->assertEquals('30d', $response['body']['range']);
$this->assertIsArray($response['body']['deployments']);
$this->assertIsArray($response['body']['deploymentsStorage']);
$this->assertIsNumeric($response['body']['deploymentsStorageTotal']);
$this->assertIsNumeric($response['body']['buildsMbSecondsTotal']);
$this->assertIsNumeric($response['body']['executionsMbSecondsTotal']);
$this->assertIsArray($response['body']['builds']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsMbSeconds']);
$this->assertIsArray($response['body']['executions']);
$this->assertIsArray($response['body']['executionsTime']);
$this->assertIsArray($response['body']['executionsMbSeconds']);
$this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']);
$this->validateDates($response['body']['executions']);
$this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']);
@ -794,15 +799,17 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(17, count($response['body']));
$this->assertEquals(21, count($response['body']));
$this->assertEquals($response['body']['range'], '30d');
$this->assertIsArray($response['body']['functions']);
$this->assertIsArray($response['body']['deployments']);
$this->assertIsArray($response['body']['deploymentsStorage']);
$this->assertIsArray($response['body']['builds']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsMbSeconds']);
$this->assertIsArray($response['body']['executions']);
$this->assertIsArray($response['body']['executionsTime']);
$this->assertIsArray($response['body']['executionsMbSeconds']);
$this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']);
$this->validateDates($response['body']['executions']);
$this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']);

View file

@ -4754,7 +4754,7 @@ trait DatabasesBase
$this->assertEquals($longtext['headers']['status-code'], 202);
for ($i = 0; $i < 1; $i++) {
for ($i = 0; $i < 10; $i++) {
$this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],

View file

@ -92,23 +92,27 @@ class FunctionsConsoleClientTest extends Scope
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(15, count($response['body']));
$this->assertEquals(19, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
$this->assertIsNumeric($response['body']['deploymentsTotal']);
$this->assertIsNumeric($response['body']['deploymentsStorageTotal']);
$this->assertIsNumeric($response['body']['buildsTotal']);
$this->assertIsNumeric($response['body']['buildsStorageTotal']);
$this->assertIsNumeric($response['body']['buildsTimeTotal']);
$this->assertIsNumeric($response['body']['buildsMbSecondsTotal']);
$this->assertIsNumeric($response['body']['executionsTotal']);
$this->assertIsNumeric($response['body']['executionsTimeTotal']);
$this->assertIsNumeric($response['body']['executionsMbSecondsTotal']);
$this->assertIsArray($response['body']['deployments']);
$this->assertIsArray($response['body']['deploymentsStorage']);
$this->assertIsArray($response['body']['builds']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsStorage']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsMbSeconds']);
$this->assertIsArray($response['body']['executions']);
$this->assertIsArray($response['body']['executionsTime']);
$this->assertIsArray($response['body']['executionsMbSeconds']);
}
/**

View file

@ -166,7 +166,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(202, $execution['headers']['status-code']);
// Wait for the first scheduled execution to be created
sleep(65);
sleep(90);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [
'content-type' => 'application/json',

View file

@ -8,6 +8,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
@ -1569,6 +1570,20 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);
// Await Aggregation
sleep(App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30));
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '24h'
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(19, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',

View file

@ -455,7 +455,7 @@ class HealthCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('/CN=www.google.com', $response['body']['name']);
$this->assertEquals('www.google.com', $response['body']['subjectSN']);
$this->assertEquals('Google Trust Services LLC', $response['body']['issuerOrganisation']);
$this->assertStringContainsString('Google Trust Services', $response['body']['issuerOrganisation']);
$this->assertIsInt($response['body']['validFrom']);
$this->assertIsInt($response['body']['validTo']);

View file

@ -511,6 +511,55 @@ trait MessagingBase
];
}
public function testSubscriberTargetSubQuery()
{
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'topicId' => 'sub-query-test',
'name' => 'sub-query-test',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$topic = $response['body'];
$prefix = uniqid();
for ($i = 1; $i <= 101; $i++) {
$response = $this->client->call(Client::METHOD_POST, '/users', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'userId' => "$prefix-$i",
'email' => "$prefix-$i@example.com",
'password' => 'password',
'name' => "User $prefix $i",
]);
$this->assertEquals(201, $response['headers']['status-code']);
$user = $response['body'];
$targets = $user['targets'] ?? [];
$this->assertGreaterThan(0, count($targets));
$target = $targets[0];
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['$id'] . '/subscribers', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'subscriberId' => $user['$id'],
'targetId' => $target['$id'],
]);
$this->assertEquals(201, $response['headers']['status-code']);
}
}
/**
* @depends testCreateSubscriber
*/

View file

@ -484,6 +484,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertIsNumeric($response['body']['bucketsTotal']);
$this->assertIsNumeric($response['body']['usersTotal']);
$this->assertIsNumeric($response['body']['filesStorageTotal']);
$this->assertIsNumeric($response['body']['deploymentStorageTotal']);
/**

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Teams;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
@ -12,4 +13,55 @@ class TeamsCustomClientTest extends Scope
use TeamsBaseClient;
use ProjectCustom;
use SideClient;
/**
* @depends testUpdateTeamMembership
*/
public function testTeamsInviteHTMLInjection($data): array
{
$teamUid = $data['teamUid'] ?? '';
$email = uniqid() . 'friend@localhost.test';
$name = 'Friend User';
$password = 'password';
// Create a user account before we create a invite so we can check if the user has permissions when it shouldn't
$user = $this->client->call(Client::METHOD_POST, '/account', [
'content-type' => 'application/json',
'x-appwrite-project' => 'console'], [
'userId' => 'unique()',
'email' => $email,
'password' => $password,
'name' => $name,
], false);
$this->assertEquals(201, $user['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'email' => $email,
'name' => $name,
'roles' => ['admin', 'editor'],
'url' => 'http://localhost:5000/join-us\"></a><h1>INJECTED</h1>'
]);
$this->assertEquals(201, $response['headers']['status-code']);
$email = $this->getLastEmail();
$encoded = 'http://localhost:5000/join-us\&quot;&gt;&lt;/a&gt;&lt;h1&gt;INJECTED&lt;/h1&gt;?';
$this->assertStringNotContainsString('<h1>INJECTED</h1>', $email['html']);
$this->assertStringContainsString($encoded, $email['html']);
$this->assertStringContainsString($encoded, $email['text']);
$response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/'.$response['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(204, $response['headers']['status-code']);
return $data;
}
}

View file

@ -290,6 +290,28 @@ trait UsersBase
$this->assertArrayNotHasKey('secret', $token['body']);
}
/**
* @depends testCreateUser
*/
public function testCreateSession(array $data): void
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['userId'] . '/sessions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(201, $response['headers']['status-code']);
$session = $response['body'];
$this->assertEquals($data['userId'], $session['userId']);
$this->assertNotEmpty($session['secret']);
$this->assertNotEmpty($session['expire']);
$this->assertEquals('server', $session['provider']);
}
/**
* Tests all optional parameters of createUser (email, phone, anonymous..)
@ -977,6 +999,7 @@ trait UsersBase
$this->assertEquals($user['headers']['status-code'], 200);
$this->assertNotEmpty($user['body']['$id']);
$this->assertEmpty($user['body']['password']);
sleep(5);
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
'content-type' => 'application/json',

File diff suppressed because it is too large Load diff