1
0
Fork 0
mirror of synced 2024-06-01 10:29:48 +12:00

Merge branch '0.16.x' of https://github.com/appwrite/appwrite into matej-qa-3

This commit is contained in:
Christy Jacob 2022-09-13 18:21:44 +00:00
commit efbe21d433
35 changed files with 639 additions and 285 deletions

View file

@ -11,6 +11,7 @@
- Compound indexes are now more flexible [#151](https://github.com/utopia-php/database/pull/151)
- `createExecution` parameter `async` default value was changed from `true` to `false` [#3781](https://github.com/appwrite/appwrite/pull/3781)
- String attribute `status` has been refactored to a Boolean attribute `enabled` in the functions collection [#3798](https://github.com/appwrite/appwrite/pull/3798)
- `time` attribute in Execution response model has been reanamed to `duration` to be more consistent with other response models. [#3801](https://github.com/appwrite/appwrite/pull/3801)
## Features
- Added the UI to see the Parent ID of all resources within the UI. [#3653](https://github.com/appwrite/appwrite/pull/3653)

View file

@ -2672,7 +2672,7 @@ $collections = [
'filters' => [],
],
[
'$id' => ID::custom('time'),
'$id' => ID::custom('duration'),
'type' => Database::VAR_FLOAT,
'format' => '',
'size' => 0,
@ -2731,9 +2731,9 @@ $collections = [
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_time'),
'$id' => ID::custom('_key_duration'),
'type' => Database::INDEX_KEY,
'attributes' => ['time'],
'attributes' => ['duration'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],

View file

@ -360,7 +360,7 @@ App::get('/v1/avatars/initials')
->action(function (string $name, int $width, int $height, string $background, Response $response, Document $user) {
$themes = [
['background' => '#F2F2F8'], // Default
['background' => '#FFA1CE'], // Default (Pink)
['background' => '#FDC584'], // Orange
['background' => '#94DBD1'], // Green
['background' => '#A1C4FF'], // Blue

View file

@ -1008,7 +1008,7 @@ App::post('/v1/functions/:functionId/executions')
'statusCode' => 0,
'response' => '',
'stderr' => '',
'time' => 0.0,
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
])));
@ -1095,11 +1095,11 @@ App::post('/v1/functions/:functionId/executions')
$execution->setAttribute('response', $executionResponse['response']);
$execution->setAttribute('stdout', $executionResponse['stdout']);
$execution->setAttribute('stderr', $executionResponse['stderr']);
$execution->setAttribute('time', $executionResponse['time']);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$interval = (new \DateTime())->diff(new \DateTime($execution->getCreatedAt()));
$execution
->setAttribute('time', (float)$interval->format('%s.%f'))
->setAttribute('duration', (float)$interval->format('%s.%f'))
->setAttribute('status', 'failed')
->setAttribute('statusCode', $th->getCode())
->setAttribute('stderr', $th->getMessage());
@ -1113,7 +1113,7 @@ App::post('/v1/functions/:functionId/executions')
->setParam('functionId', $function->getId())
->setParam('executions.{scope}.compute', 1)
->setParam('executionStatus', $execution->getAttribute('status', ''))
->setParam('executionTime', $execution->getAttribute('time')); // ms
->setParam('executionTime', $execution->getAttribute('duration')); // ms
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);

View file

@ -503,7 +503,7 @@ App::post('/v1/execution')
$ch = \curl_init();
$body = \json_encode([
'env' => $vars,
'variables' => $vars,
'payload' => $data,
'timeout' => $timeout
]);
@ -574,7 +574,7 @@ App::post('/v1/execution')
'response' => \mb_strcut($res, 0, 1000000), // Limit to 1MB
'stdout' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
'time' => $executionTime,
'duration' => $executionTime,
];
/** Update swoole table */

View file

@ -108,7 +108,7 @@ $cli
$time = DateTime::now();
$certificates = $dbForConsole->find('certificates', [
Query::lessThanEqual('attempts', 5), // Maximum 5 attempts
Query::lessThan('attempts', 5), // Maximum 5 attempts
Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew)
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
]);

View file

@ -46,8 +46,20 @@ $escapedPermissions = \array_map(function ($perm) {
<?php endif; ?>
:value="rawPermissions"/>
<table class="u-table-layout-normal" data-ls-attrs="x-init=load({{<?php if (!empty($data)) echo $data . '.$permissions' ?>}})">
<thead>
<datalist id="types">
<option value="user:">
<option value="team:">
<option value="member:">
<option value="users">
<option value="guests">
<option value="any">
</datalist>
<table
class="u-table-layout-normal"
data-ls-attrs="x-init=load({{<?php if (!empty($data)) echo $data . '.$permissions' ?>}})">
<thead x-show="permissions.length > 0">
<tr>
<th>Role</th>
<?php foreach ($permissions as $permission): ?>
@ -60,7 +72,16 @@ $escapedPermissions = \array_map(function ($perm) {
<template x-for="(permission, index) in permissions">
<tr>
<td>
<p x-text="permission.role"></p>
<input
required
autocomplete="off"
:id="'<?php echo $form; ?>Input' + index"
name="<?php echo $form; ?>"
form="<?php echo $form ?>"
list="types"
type="text"
x-model="permission.role"
@keyup="updatePermission(index)"/>
</td>
<?php foreach ($escapedPermissions as $permission): ?>
<td>
@ -78,38 +99,11 @@ $escapedPermissions = \array_map(function ($perm) {
</td>
</tr>
</template>
<tr x-data="permissionsRow"
@addrow<?php echo \strtolower($form); ?>.window="addPermission('<?php echo $form; ?>', role, { <?php echo \implode(',', $escapedPermissions) ?> })">
<td>
<datalist id="types">
<option value="user:">
<option value="team:">
<option value="users">
<option value="guests">
<option value="any">
</datalist>
<input
required
id="<?php echo $form; ?>Input"
name="<?php echo $form; ?>"
form="<?php echo $form ?>"
list="types"
type="text"
x-model="role" />
</td>
<?php foreach ($escapedPermissions as $permission): ?>
<td>
<input type="checkbox" name="<?php echo $permission ?>" x-model="<?php echo $permission; ?>"/>
</td>
<?php endforeach; ?>
<td></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="<?php \count($permissions) + 2 ?>">
<button type="button" class="margin-top-small reverse" @click="$dispatch('addrow<?php echo \strtolower($form); ?>')">Add</button>
<button type="button" class="margin-top-small reverse" @click="addPermission('<?php echo $form ?>')">Add Role</button>
</td>
</tr>
</tfoot>

View file

@ -508,7 +508,7 @@ $permissions = $this->getParam('permissions', null);
<li data-state="/console/databases/collection/settings?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
<div class="row responsive">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form') ?>"></form>
@ -607,7 +607,7 @@ $permissions = $this->getParam('permissions', null);
data-success="alert,trigger,redirect"
data-success-param-alert-text="Collection deleted successfully"
data-success-param-trigger-events="databases.deleteCollection"
data-success-param-redirect-url="/console/databases?project={{router.params.project}}"
data-success-param-redirect-url="/console/databases?project={{router.params.project}}&databaseId={{router.params.databaseId}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete collection"
data-failure-param-alert-classname="error">

View file

@ -416,7 +416,7 @@ sort($patterns);
<span data-ls-bind="{{execution.trigger}}"></span>
</td>
<td data-title="Time: ">
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.time|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.duration|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'waiting' || {{execution.status}} === 'processing'">-</span>
</td>
<td data-title="">

View file

@ -8,6 +8,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\DateTime;
use Utopia\Database\ID;
use Utopia\Database\Query;
use Utopia\Domains\Domain;
@ -100,8 +101,11 @@ class CertificatesV1 extends Worker
throw new Exception('Renew isn\'t required.');
}
// Prepare folder name for certbot. Using this helps prevent miss-match in LetsEncrypt configuration when renewing certificate
$folder = ID::unique();
// Generate certificate files using Let's Encrypt
$letsEncryptData = $this->issueCertificate($domain->get(), $email);
$letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email);
// Command succeeded, store all data into document
// We store stderr too, because it may include warnings
@ -111,7 +115,7 @@ class CertificatesV1 extends Worker
]));
// Give certificates to Traefik
$this->applyCertificateFiles($domain->get(), $letsEncryptData);
$this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData);
// Update certificate info stored in database
$certificate->setAttribute('renewDate', $this->getRenewDate($domain->get()));
@ -125,6 +129,9 @@ class CertificatesV1 extends Worker
$attempts = $certificate->getAttribute('attempts', 0) + 1;
$certificate->setAttribute('attempts', $attempts);
// Store cuttent time as renew date to ensure another attempt in next maintenance cycle
$certificate->setAttribute('renewDate', DateTime::now());
// Send email to security email
$this->notifyError($domain->get(), $e->getMessage(), $attempts);
} finally {
@ -259,11 +266,12 @@ class CertificatesV1 extends Worker
/**
* LetsEncrypt communication to issue certificate (using certbot CLI)
*
* @param string $folder Folder into which certificates should be generated
* @param string $domain Domain to generate certificate for
*
* @return array Named array with keys 'stdout' and 'stderr', both string
*/
private function issueCertificate(string $domain, string $email): array
private function issueCertificate(string $folder, string $domain, string $email): array
{
$stdout = '';
$stderr = '';
@ -271,6 +279,7 @@ class CertificatesV1 extends Worker
$staging = (App::isProduction()) ? '' : ' --dry-run';
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
. " --email " . $email
. " --cert-name " . $folder
. " -w " . APP_STORAGE_CERTIFICATES
. " -d {$domain}", '', $stdout, $stderr);
@ -290,9 +299,9 @@ class CertificatesV1 extends Worker
*
* @param string $domain Domain which certificate was generated for
*
* @return int
* @return string
*/
private function getRenewDate(string $domain): int
private function getRenewDate(string $domain): string
{
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
$certData = openssl_x509_parse(file_get_contents($certPath));
@ -305,11 +314,12 @@ class CertificatesV1 extends Worker
* Method to take files from Let's Encrypt, and put it into Traefik.
*
* @param string $domain Domain which certificate was generated for
* @param string $folder Folder in which certificates were generated
* @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error
*
* @return void
*/
private function applyCertificateFiles(string $domain, array $letsEncryptData): void
private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void
{
// Prepare folder in storage for domain
$path = APP_STORAGE_CERTIFICATES . '/' . $domain;
@ -319,20 +329,20 @@ class CertificatesV1 extends Worker
}
}
// Move generated files from certbot into our storage
if (!@\rename('/etc/letsencrypt/live/' . $domain . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
// Move generated files
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $domain . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $domain . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $domain . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}

View file

@ -248,7 +248,7 @@ class FunctionsV1 extends Worker
'statusCode' => 0,
'response' => '',
'stderr' => '',
'time' => 0.0,
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
]));
@ -301,11 +301,11 @@ class FunctionsV1 extends Worker
->setAttribute('response', $executionResponse['response'])
->setAttribute('stdout', $executionResponse['stdout'])
->setAttribute('stderr', $executionResponse['stderr'])
->setAttribute('time', $executionResponse['time']);
->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$interval = (new \DateTime())->diff(new \DateTime($execution->getCreatedAt()));
$execution
->setAttribute('time', (float)$interval->format('%s.%f'))
->setAttribute('duration', (float)$interval->format('%s.%f'))
->setAttribute('status', 'failed')
->setAttribute('statusCode', $th->getCode())
->setAttribute('stderr', $th->getMessage());
@ -367,7 +367,7 @@ class FunctionsV1 extends Worker
->setParam('functionId', $function->getId())
->setParam('executions.{scope}.compute', 1)
->setParam('executionStatus', $execution->getAttribute('status', ''))
->setParam('executionTime', $execution->getAttribute('time'))
->setParam('executionTime', $execution->getAttribute('duration'))
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->submit();

View file

@ -1,3 +1,33 @@
## 7.0.0-dev.2
### NEW
* Support for Appwrite 1.0.0-RC1
* More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version`
* Helper classes and methods for Permissions, Roles and IDs
* Helper methods to suport new queries
* All Dates and times are now returned in the ISO 8601 format
* Execution Model now has an additional `stdout` attribute
* Endpoint for creating DateTime attribute
* User imports API with support for multiple hashing algorithms
* CRUD API for functions environment variables
* `createBucket` now supports different compression algorithms
### BREAKING CHANGES
* `databaseId` is no longer part of the `Database` Service constructor. `databaseId` will be part of the respective methods of the database service.
* The `Users.create()` method signature has now been updated to include a `phone` parameter
* `color` attribute is no longer supported in the Avatars Service
* The `number` argument in phone endpoints have been renamed to `phone`
* List endpoints no longer support `limit`, `offset`, `cursor`, `cursorDirection`, `orderAttributes`, `orderTypes` as they have been moved to the `queries` array
* `read` and `write` permission have been deprecated and they are now included in the `permissions` array
* Parameter `permission` for collections and buckets are now renamed to `documentSecurity` & `fileSecurity` respectively
* Renamed methods of the Query helper
1. `lesser` renamed to `lessThan`
2. `lesserEqual` renamed to `lessThanEqual`
3. `greater` renamed to `greaterThan`
4. `greaterEqual` renamed to `greaterThanEqual`
**Full Changelog for Appwrite 1.0.0-RC1 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md
## 6.0.1
* Dependency upgrades
* Doc comments updates

View file

@ -1,3 +1,29 @@
## 8.0.0-dev.2 Latest
### NEW
* Support for Appwrite 1.0.0-RC1
* More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version`
* Helper classes and methods for Permissions, Roles and IDs
* Helper methods to suport new queries
* All Dates and times are now returned in the ISO 8601 format
### BREAKING CHANGES
* `databaseId` is no longer part of the `Database` Service constructor. `databaseId` will be part of the respective methods of the database service.
* `color` attribute is no longer supported in the Avatars Service
* The `number` argument in phone endpoints have been renamed to `phone`
* List endpoints no longer support `limit`, `offset`, `cursor`, `cursorDirection`, `orderAttributes`, `orderTypes` as they have been moved to the `queries` array
* `read` and `write` permission have been deprecated and they are now included in the `permissions` array
* Renamed methods of the Query helper
1. `lesser` renamed to `lessThan`
2. `lesserEqual` renamed to `lessThanEqual`
3. `greater` renamed to `greaterThan`
4. `greaterEqual` renamed to `greaterThanEqual`
* `User` response model is now renamed to `Account`
**Full Changelog for Appwrite 1.0.0-RC1 can be found here**:
https://github.com/appwrite/appwrite/blob/master/CHANGES.md
## 7.0.0
* **BREAKING** Switched to using [flutter_web_auth_2](https://pub.dev/packages/flutter_web_auth_2), check Getting Started section in Readme for changes (Android and Web will require adjustments for OAuth to work properly)
* Fixes Concurrent modification issue

167
docs/tutorials/add-route.md Normal file
View file

@ -0,0 +1,167 @@
# Adding Route 🛡
This document is part of the Appwrite contributors' guide. Before you continue reading this document make sure you have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/master/CODE_OF_CONDUCT.md) and the [Contributing Guide](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md).
### 1. Alias
Setting an alias allows the route to be also accessible from the alias URL.
The first parameter specifies the alias URL, the second parameter specifies default values for route parameters.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
```
### 2. Description
Used as an abstract description of the route.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->desc('Create File')
```
### 3. Groups
Groups array is used to group one or more routes with one or more hooks functionality.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->groups(['api'])
```
In the above example groups() is used to define the current route as part of the routes that shares a common init middleware hook.
```php
App::init()
->groups(['api'])
->action(
some code.....
);
```
### 4. The Labels Mechanism
Labels are very strait forward and easy to use and understand, but in the same time are very robust.
Labels are passed from the controllers route and used to pick up key-value pairs to be handled in a centralized place
along the road.
Labels can be used to pass a pattern in order to be replaced in the other end.
Appwrite uses different labels to achieve different things, for example:
#### Scope
* scope - Defines the route permissions scope.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->label('scope', 'files.write')
```
#### Audit
* audits.event - Identify the log in human-readable text.
* audits.userId - Signals the extraction of $userId in places that it's not available natively.
* audits.resource - Signals the extraction part of the resource.
```php
App::post('/v1/account/create')
->label('audits.event', 'account.create')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
```
#### SDK
* sdk.auth - Array of authentication types is passed in order to impose different authentication methods in different situations.
* sdk.namespace - Refers to the route namespace.
* sdk.method - Refers to the sdk method that needs to called.
* sdk.description - Description of the route,using markdown format.
* sdk.sdk.response.code - Refers to the route http response status code expected.
* sdk.auth.response.model - Refers the route http response expected.
```php
App::post('/v1/account/jwt')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/account/create-jwt.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
```
#### Cache
* cache - When set to true, signal the use of file cache.
* cache.resource - Identifies the cached resource.
```php
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('cache', true)
->label('cache.resource', 'file/{request.fileId}')
```
#### Abuse
* abuse-key - Specifies routes unique abuse key.
* abuse-limit - Specifies the number of times the route can be requested in a time frame, per route.
* abuse-time - Specifies the time frame (in seconds) relevancy of the all other abuse definitions, per route.
When using the example below, we configure the abuse mechanism to allow this key combination
constructed from the combination of the ip, http method, url, userId to hit the route maximum 60 times in 1 hour (60 seconds * 60 minutes).
```php
App::post('/v1/storage/buckets/:bucketId/files')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', 60)
->label('abuse-time', 3600)
```
#### Events
* event - A pattern that is associated with the route in behalf of realtime messaging.
Placeholders marked as `[]` are parsed and replaced with their real values.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->label('event', 'buckets.[bucketId].files.[fileId].create')
```
#### Usage
* usage.metric - The metric the route generates.
* usage.params - Additional parameters the metrics can have.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->label('usage.metric', 'files.{scope}.requests.create')
->label('usage.params', ['bucketId:{request.bucketId}'])
```
### 5. Param
As the name implies, `param()` is used to define a request parameter.
`param()` accepts 6 parameters :
* A key (name)
* A default value
* An instance of a validator class,This can also accept a callback that returns a validator instance. Dependency injection is supported for the callback.
* Description of the parameter
* Is the route optional
* An array of injections
```php
App::get('/v1/account/logs')
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
```
### 6. inject
inject is used to inject dependencies pre-bounded to the app.
```php
App::post('/v1/storage/buckets/:bucketId/files')
->inject('user')
```
In the example above, the user object is injected into the route pre-bounded using `App::setResource()`.
```php
App::setResource('user', function() {
some code...
});
```
### 6. Action
Action populates the actual routes code and has to be very clear and understandable. A good route stay simple and doesn't contain complex logic. An action is where we describe our business need in code, and combine different libraries to work together and tell our story.
```php
App::post('/v1/account/sessions/anonymous')
->action(function (Request $request) {
some code...
});
```

View file

@ -3967,7 +3967,7 @@ return false;};return{isRTL:isRTL,};},true);})(window);(function(window){"use st
let size=element.dataset["size"]||80;let name=$value.name||$value||"";name=(typeof name!=='string')?'--':name;return def="/v1/avatars/initials?project=console"+"&name="+
encodeURIComponent(name)+"&width="+
size+"&height="+
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let now=new Date();now.setMinutes(now.getMinutes()+now.getTimezoneOffset());let timestamp=new Date(now.toISOString()).getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let timestamp=new Date().getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
let value=seconds;if(seconds>=31536000){value=Math.floor(seconds/31536000);unit="year";}
else if(seconds>=86400){value=Math.floor(seconds/86400);unit="day";}
else if(seconds>=3600){value=Math.floor(seconds/3600);unit="hour";}
@ -4015,14 +4015,13 @@ if(this.action){event+=`.${this.action}`;}
if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;}
this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

View file

@ -555,7 +555,7 @@ return false;};return{isRTL:isRTL,};},true);})(window);(function(window){"use st
let size=element.dataset["size"]||80;let name=$value.name||$value||"";name=(typeof name!=='string')?'--':name;return def="/v1/avatars/initials?project=console"+"&name="+
encodeURIComponent(name)+"&width="+
size+"&height="+
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let now=new Date();now.setMinutes(now.getMinutes()+now.getTimezoneOffset());let timestamp=new Date(now.toISOString()).getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let timestamp=new Date().getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
let value=seconds;if(seconds>=31536000){value=Math.floor(seconds/31536000);unit="year";}
else if(seconds>=86400){value=Math.floor(seconds/86400);unit="day";}
else if(seconds>=3600){value=Math.floor(seconds/3600);unit="hour";}
@ -603,14 +603,13 @@ if(this.action){event+=`.${this.action}`;}
if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;}
this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

View file

@ -47,13 +47,7 @@ window.ls.filter
.add("timeSince", function ($value) {
$value = new Date($value).getTime();
/**
* Adapt to timezone UTC.
*/
let now = new Date();
now.setMinutes(now.getMinutes() + now.getTimezoneOffset());
let timestamp = new Date(now.toISOString()).getTime();
let timestamp = new Date().getTime();
let seconds = Math.floor((timestamp - $value) / 1000);
let unit = "second";
let direction = "ago";

View file

@ -22,10 +22,15 @@
}
})
if (existing === undefined) {
this.permissions.push({
let newPermission = {
role,
[type]: true,
});
create: false,
read: false,
update: false,
xdelete: false,
};
newPermission[type] = true;
this.permissions.push(newPermission);
}
if (index !== -1) {
existing[type] = true;
@ -33,22 +38,18 @@
}
});
},
addPermission(formId, role, permissions) {
if (!this.validate(formId, role, permissions)) {
addPermission(formId) {
if (this.permissions.length > 0
&& !this.validate(formId, this.permissions.length - 1)) {
return;
}
Object.entries(permissions).forEach(entry => {
let [type, enabled] = entry;
type = this.parseOutputPermission(type);
if (enabled) {
this.rawPermissions.push(this.buildPermission(type, role));
}
});
this.permissions.push({
role,
...permissions,
role: '',
create: false,
read: false,
update: false,
xdelete: false,
});
this.reset();
},
updatePermission(index) {
// Because the x-model does not update before the click event,
@ -106,31 +107,26 @@
}
return key;
},
validate(formId, role, permissions) {
validate(formId, index) {
const form = document.getElementById(formId);
const input = document.getElementById(`${formId}Input`);
const input = document.getElementById(`${formId}Input${index}`);
const permission = this.permissions[index];
input.setCustomValidity('');
if (!Object.values(permissions).some(p => p)) {
if (permission.role === '') {
input.setCustomValidity('Role is required');
} else if (!Object.entries(permission).some(([k, v]) => !k.includes('role') && v)) {
input.setCustomValidity('No permissions selected');
}
if (this.permissions.some(p => p.role === role)) {
} else if (this.permissions.some(p => p.role === permission.role && p !== permission)) {
input.setCustomValidity('Role entry already exists');
}
return form.reportValidity();
}
}));
Alpine.data('permissionsRow', () => ({
role: '',
read: false,
create: false,
update: false,
xdelete: false,
reset() {
this.role = '';
this.read = this.create = this.update = this.xdelete = false;
},
prevent(event) {
event.preventDefault();
event.stopPropagation();
}
}));
});

View file

@ -431,11 +431,14 @@ class Auth
continue;
}
if (isset($node['teamId']) && isset($node['roles'])) {
if (isset($node['$id']) && isset($node['teamId'])) {
$roles[] = Role::team($node['teamId'])->toString();
$roles[] = Role::member($node['$id'])->toString();
foreach ($node['roles'] as $nodeRole) { // Set all team roles
$roles[] = Role::team($node['teamId'], $nodeRole)->toString();
if (isset($node['roles'])) {
foreach ($node['roles'] as $nodeRole) { // Set all team roles
$roles[] = Role::team($node['teamId'], $nodeRole)->toString();
}
}
}
}

View file

@ -323,7 +323,6 @@ class TimeSeries extends Calculator
$document->setAttribute('value', $value)
);
}
$this->latestTime[$metric][$period] = $time;
} catch (\Exception $e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
@ -397,6 +396,7 @@ class TimeSeries extends Calculator
$value,
0
);
$this->latestTime[$metric][$period['key']] = $point['time'];
}
}
} catch (\Exception $e) { // if projects are deleted this might fail

View file

@ -8,7 +8,7 @@ class Executions extends Base
'trigger',
'status',
'statusCode',
'time'
'duration'
];
/**

View file

@ -59,7 +59,7 @@ class Build extends Model
])
->addRule('duration', [
'type' => self::TYPE_INTEGER,
'description' => 'The build time in seconds.',
'description' => 'The build duration in seconds.',
'default' => 0,
'example' => 0,
])

View file

@ -78,9 +78,9 @@ class Execution extends Model
'default' => '',
'example' => '',
])
->addRule('time', [
->addRule('duration', [
'type' => self::TYPE_FLOAT,
'description' => 'The script execution time in seconds.',
'description' => 'The script execution duration in seconds.',
'default' => 0,
'example' => 0.400,
])

View file

@ -26,8 +26,7 @@ class UsageTest extends Scope
parent::setUp();
}
#[Retry(count: 1)]
public function testUsersStats(): array
public function testPrepareUsersStats(): array
{
$project = $this->getProject(true);
$projectId = $project['$id'];
@ -63,8 +62,27 @@ class UsageTest extends Scope
}
}
return [
'projectId' => $projectId,
'headers' => $headers,
'usersCount' => $usersCount,
'requestsCount' => $requestsCount
];
}
/**
* @depends testPrepareUsersStats
*/
#[Retry(count: 1)]
public function testUsersStats(array $data): array
{
sleep(35);
$projectId = $data['projectId'];
$headers = $data['headers'];
$usersCount = $data['usersCount'];
$requestsCount = $data['requestsCount'];
// console request
$cheaders = [
'origin' => 'http://localhost',
@ -94,8 +112,7 @@ class UsageTest extends Scope
}
/** @depends testUsersStats */
#[Retry(count: 1)]
public function testStorageStats(array $data): array
public function testPrepareStorageStats(array $data): array
{
$projectId = $data['projectId'];
$headers = $data['headers'];
@ -190,6 +207,40 @@ class UsageTest extends Scope
}
}
return array_merge($data, [
'bucketId' => $bucketId,
'bucketsCount' => $bucketsCount,
'requestsCount' => $requestsCount,
'storageTotal' => $storageTotal,
'bucketsCreate' => $bucketsCreate,
'bucketsDelete' => $bucketsDelete,
'bucketsRead' => $bucketsRead,
'filesCount' => $filesCount,
'filesRead' => $filesRead,
'filesCreate' => $filesCreate,
'filesDelete' => $filesDelete,
]);
}
/**
* @depends testPrepareStorageStats
*/
#[Retry(count: 1)]
public function testStorageStats(array $data): array
{
$projectId = $data['projectId'];
$bucketId = $data['bucketId'];
$bucketsCount = $data['bucketsCount'];
$requestsCount = $data['requestsCount'];
$storageTotal = $data['storageTotal'];
$bucketsCreate = $data['bucketsCreate'];
$bucketsDelete = $data['bucketsDelete'];
$bucketsRead = $data['bucketsRead'];
$filesCount = $data['filesCount'];
$filesRead = $data['filesRead'];
$filesCreate = $data['filesCreate'];
$filesDelete = $data['filesDelete'];
sleep(35);
// console request
@ -239,8 +290,7 @@ class UsageTest extends Scope
}
/** @depends testStorageStats */
#[Retry(count: 1)]
public function testDatabaseStats(array $data): array
public function testPrepareDatabaseStats(array $data): array
{
$headers = $data['headers'];
$projectId = $data['projectId'];
@ -366,6 +416,58 @@ class UsageTest extends Scope
}
}
$data = array_merge($data, [
'databaseId' => $databaseId,
'collectionId' => $collectionId,
'requestsCount' => $requestsCount,
'databasesCount' => $databasesCount,
'databasesCreate' => $databasesCreate,
'databasesRead' => $databasesRead,
'databasesDelete' => $databasesDelete,
'collectionsCount' => $collectionsCount,
'collectionsCreate' => $collectionsCreate,
'collectionsRead' => $collectionsRead,
'collectionsUpdate' => $collectionsUpdate,
'collectionsDelete' => $collectionsDelete,
'documentsCount' => $documentsCount,
'documentsCreate' => $documentsCreate,
'documentsRead' => $documentsRead,
'documentsDelete' => $documentsDelete,
]);
return $data;
}
/** @depends testPrepareDatabaseStats */
#[Retry(count: 1)]
public function testDatabaseStats(array $data): array
{
$headers = $data['headers'];
$projectId = $data['projectId'];
$databaseId = $data['databaseId'];
$collectionId = $data['collectionId'];
$requestsCount = $data['requestsCount'];
$databasesCount = $data['databasesCount'];
$databasesCreate = $data['databasesCreate'];
$databasesRead = $data['databasesRead'];
$databasesDelete = $data['databasesDelete'];
$collectionsCount = $data['collectionsCount'];
$collectionsCreate = $data['collectionsCreate'];
$collectionsRead = $data['collectionsRead'];
$collectionsUpdate = $data['collectionsUpdate'];
$collectionsDelete = $data['collectionsDelete'];
$documentsCount = $data['documentsCount'];
$documentsCreate = $data['documentsCreate'];
$documentsRead = $data['documentsRead'];
$documentsDelete = $data['documentsDelete'];
sleep(35);
// check datbase stats
@ -440,8 +542,7 @@ class UsageTest extends Scope
/** @depends testDatabaseStats */
#[Retry(count: 1)]
public function testFunctionsStats(array $data): void
public function testPrepareFunctionsStats(array $data): array
{
$headers = $data['headers'];
$functionId = '';
@ -505,7 +606,7 @@ class UsageTest extends Scope
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$this->assertEquals($functionId, $execution['body']['functionId']);
$executionTime += (int) ($execution['body']['time'] * 1000);
$executionTime += (int) ($execution['body']['duration'] * 1000);
if ($execution['body']['status'] == 'failed') {
$failures++;
} elseif ($execution['body']['status'] == 'completed') {
@ -524,7 +625,27 @@ class UsageTest extends Scope
} elseif ($execution['body']['status'] == 'completed') {
$executions++;
}
$executionTime += (int) ($execution['body']['time'] * 1000);
$executionTime += (int) ($execution['body']['duration'] * 1000);
$data = array_merge($data, [
'functionId' => $functionId,
'executionTime' => $executionTime,
'executions' => $executions,
'failures' => $failures,
]);
return $data;
}
/** @depends testPrepareFunctionsStats */
#[Retry(count: 1)]
public function testFunctionsStats(array $data): void
{
$headers = $data['headers'];
$functionId = $data['functionId'];
$executionTime = $data['executionTime'];
$executions = $data['executions'];
$failures = $data['failures'];
sleep(25);

View file

@ -608,7 +608,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(0, $execution['body']['statusCode']);
$this->assertEquals('', $execution['body']['response']);
$this->assertEquals('', $execution['body']['stderr']);
$this->assertEquals(0, $execution['body']['time']);
$this->assertEquals(0, $execution['body']['duration']);
sleep(5);
@ -631,7 +631,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('8.0', $execution['body']['response']);
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertEquals('', $execution['body']['stderr']);
$this->assertLessThan(0.500, $execution['body']['time']);
$this->assertLessThan(0.500, $execution['body']['duration']);
/**
* Test for FAILURE
@ -750,7 +750,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('PHP', $execution['body']['response']);
$this->assertStringContainsString('8.0', $execution['body']['response']);
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertLessThan(0.500, $execution['body']['time']);
$this->assertLessThan(0.500, $execution['body']['duration']);
return $data;
}
@ -909,9 +909,9 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertEquals($executions['body']['executions'][0]['status'], 'failed');
$this->assertEquals($executions['body']['executions'][0]['statusCode'], 500);
$this->assertGreaterThan(2, $executions['body']['executions'][0]['time']);
$this->assertLessThan(6, $executions['body']['executions'][0]['time']);
$this->assertGreaterThan(4, $executions['body']['executions'][0]['time']);
$this->assertGreaterThan(2, $executions['body']['executions'][0]['duration']);
$this->assertLessThan(6, $executions['body']['executions'][0]['duration']);
$this->assertGreaterThan(4, $executions['body']['executions'][0]['duration']);
$this->assertEquals($executions['body']['executions'][0]['response'], '');
$this->assertEquals($executions['body']['executions'][0]['stderr'], 'An internal curl error has occurred within the executor! Error Msg: Operation timed out');

View file

@ -2,7 +2,7 @@
'req' variable has:
'headers' - object with request headers
'payload' - object with request body data
'env' - object with environment variables
'variables' - object with function variables
'res' variable has:
'send(text, status)' - function to return text response. Status code defaults to 200
'json(obj, status)' - function to return JSON response. Status code defaults to 200
@ -12,18 +12,18 @@
Future<void> start(final request, final response) async {
response.json({
'APPWRITE_FUNCTION_ID' : request.env['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' : request.env['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' : request.env['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' : request.env['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' : request.env['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' : request.env['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' : request.env['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' : request.env['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' : request.env['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' : request.env['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' : request.env['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' : request.env['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' : request.env['CUSTOM_VARIABLE']
'APPWRITE_FUNCTION_ID' : request.variables['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' : request.variables['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' : request.variables['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' : request.variables['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' : request.variables['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' : request.variables['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' : request.variables['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' : request.variables['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' : request.variables['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' : request.variables['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' : request.variables['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' : request.variables['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' : request.variables['CUSTOM_VARIABLE']
});
}

View file

@ -2,7 +2,7 @@
'req' variable has:
'headers' - object with request headers
'payload' - object with request body data
'env' - object with environment variables
'variables' - object with function variables
'res' variable has:
'send(text, status)' - function to return text response. Status code defaults to 200
'json(obj, status)' - function to return JSON response. Status code defaults to 200
@ -12,18 +12,18 @@
module.exports = async(req, res) => {
res.json({
'APPWRITE_FUNCTION_ID' : req.env.APPWRITE_FUNCTION_ID,
'APPWRITE_FUNCTION_NAME' : req.env.APPWRITE_FUNCTION_NAME,
'APPWRITE_FUNCTION_DEPLOYMENT' : req.env.APPWRITE_FUNCTION_DEPLOYMENT,
'APPWRITE_FUNCTION_TRIGGER' : req.env.APPWRITE_FUNCTION_TRIGGER,
'APPWRITE_FUNCTION_RUNTIME_NAME' : req.env.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : req.env.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_FUNCTION_EVENT' : req.env.APPWRITE_FUNCTION_EVENT,
'APPWRITE_FUNCTION_EVENT_DATA' : req.env.APPWRITE_FUNCTION_EVENT_DATA,
'APPWRITE_FUNCTION_DATA' : req.env.APPWRITE_FUNCTION_DATA,
'APPWRITE_FUNCTION_USER_ID' : req.env.APPWRITE_FUNCTION_USER_ID,
'APPWRITE_FUNCTION_JWT' : req.env.APPWRITE_FUNCTION_JWT,
'APPWRITE_FUNCTION_PROJECT_ID' : req.env.APPWRITE_FUNCTION_PROJECT_ID,
'CUSTOM_VARIABLE' : req.env.CUSTOM_VARIABLE
'APPWRITE_FUNCTION_ID' : req.variables.APPWRITE_FUNCTION_ID,
'APPWRITE_FUNCTION_NAME' : req.variables.APPWRITE_FUNCTION_NAME,
'APPWRITE_FUNCTION_DEPLOYMENT' : req.variables.APPWRITE_FUNCTION_DEPLOYMENT,
'APPWRITE_FUNCTION_TRIGGER' : req.variables.APPWRITE_FUNCTION_TRIGGER,
'APPWRITE_FUNCTION_RUNTIME_NAME' : req.variables.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : req.variables.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_FUNCTION_EVENT' : req.variables.APPWRITE_FUNCTION_EVENT,
'APPWRITE_FUNCTION_EVENT_DATA' : req.variables.APPWRITE_FUNCTION_EVENT_DATA,
'APPWRITE_FUNCTION_DATA' : req.variables.APPWRITE_FUNCTION_DATA,
'APPWRITE_FUNCTION_USER_ID' : req.variables.APPWRITE_FUNCTION_USER_ID,
'APPWRITE_FUNCTION_JWT' : req.variables.APPWRITE_FUNCTION_JWT,
'APPWRITE_FUNCTION_PROJECT_ID' : req.variables.APPWRITE_FUNCTION_PROJECT_ID,
'CUSTOM_VARIABLE' : req.variables.CUSTOM_VARIABLE
});
}

View file

@ -4,17 +4,17 @@ return function ($request, $response) {
\var_dump("Amazing Function Log"); // We test logs (stdout) visibility with this
$response->json([
'APPWRITE_FUNCTION_ID' => $request['env']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['env']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['env']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['env']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['env']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['env']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['env']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['env']['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => $request['env']['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => $request['env']['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => $request['env']['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' => $request['env']['APPWRITE_FUNCTION_PROJECT_ID'],
'APPWRITE_FUNCTION_ID' => $request['variables']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['variables']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['variables']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['variables']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['variables']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['variables']['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => $request['variables']['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => $request['variables']['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => $request['variables']['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' => $request['variables']['APPWRITE_FUNCTION_PROJECT_ID'],
]);
};

View file

@ -2,14 +2,14 @@
return function ($request, $response) {
return $response->json([
'APPWRITE_FUNCTION_ID' => $request['env']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['env']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['env']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['env']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['env']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['env']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['env']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['env']['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_ID' => $request['variables']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['variables']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['variables']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['variables']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['variables']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['variables']['APPWRITE_FUNCTION_EVENT_DATA'],
'UNICODE_TEST' => "êä"
]);
};

View file

@ -2,14 +2,14 @@
return function ($request, $response) {
return $response->json([
'APPWRITE_FUNCTION_ID' => $request['env']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['env']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['env']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['env']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['env']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['env']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['env']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['env']['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_ID' => $request['variables']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['variables']['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => $request['variables']['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => $request['variables']['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request['variables']['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request['variables']['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request['variables']['APPWRITE_FUNCTION_EVENT_DATA'],
'UNICODE_TEST' => "êä"
]);
};

View file

@ -3,7 +3,7 @@ import json
# 'req' variable has:
# 'headers' - object with request headers
# 'payload' - object with request body data
# 'env' - object with environment variables
# 'variables' - object with function variables
# 'res' variable has:
# 'send(text, status)' - function to return text response. Status code defaults to 200
# 'json(obj, status)' - function to return JSON response. Status code defaults to 200
@ -12,17 +12,17 @@ import json
def main(request, response):
return response.json({
'APPWRITE_FUNCTION_ID' : request.env['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' : request.env['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' : request.env['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' : request.env['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' : request.env['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' : request.env['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' : request.env['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' : request.env['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' : request.env['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' : request.env['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' : request.env['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' : request.env['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' : request.env['CUSTOM_VARIABLE'],
'APPWRITE_FUNCTION_ID' : request.variables['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' : request.variables['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' : request.variables['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' : request.variables['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' : request.variables['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' : request.variables['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' : request.variables['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' : request.variables['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' : request.variables['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' : request.variables['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' : request.variables['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' : request.variables['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' : request.variables['CUSTOM_VARIABLE'],
})

View file

@ -2,7 +2,7 @@
'req' variable has:
'headers' - object with request headers
'payload' - object with request body data
'env' - object with environment variables
'variables' - object with function variables
'res' variable has:
'send(text, status)' - function to return text response. Status code defaults to 200
'json(obj, status)' - function to return JSON response. Status code defaults to 200
@ -12,18 +12,18 @@
def main(request, response)
return response.json({
'APPWRITE_FUNCTION_ID' => request.env['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => request.env['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => request.env['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => request.env['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => request.env['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => request.env['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => request.env['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => request.env['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => request.env['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => request.env['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => request.env['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' => request.env['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' => request.env['CUSTOM_VARIABLE']
'APPWRITE_FUNCTION_ID' => request.variables['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => request.variables['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_DEPLOYMENT' => request.variables['APPWRITE_FUNCTION_DEPLOYMENT'],
'APPWRITE_FUNCTION_TRIGGER' => request.variables['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => request.variables['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => request.variables['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => request.variables['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => request.variables['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => request.variables['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => request.variables['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => request.variables['APPWRITE_FUNCTION_JWT'],
'APPWRITE_FUNCTION_PROJECT_ID' => request.variables['APPWRITE_FUNCTION_PROJECT_ID'],
'CUSTOM_VARIABLE' => request.variables['CUSTOM_VARIABLE']
})
end

View file

@ -1,17 +1,17 @@
func main(req: RequestValue, res: RequestResponse) throws -> RequestResponse {
return res.json(data: [
"APPWRITE_FUNCTION_ID": req.env["APPWRITE_FUNCTION_ID"],
"APPWRITE_FUNCTION_NAME": req.env["APPWRITE_FUNCTION_NAME"],
"APPWRITE_FUNCTION_DEPLOYMENT": req.env["APPWRITE_FUNCTION_DEPLOYMENT"],
"APPWRITE_FUNCTION_TRIGGER": req.env["APPWRITE_FUNCTION_TRIGGER"],
"APPWRITE_FUNCTION_RUNTIME_NAME": req.env["APPWRITE_FUNCTION_RUNTIME_NAME"],
"APPWRITE_FUNCTION_RUNTIME_VERSION": req.env["APPWRITE_FUNCTION_RUNTIME_VERSION"],
"APPWRITE_FUNCTION_EVENT": req.env["APPWRITE_FUNCTION_EVENT"],
"APPWRITE_FUNCTION_EVENT_DATA": req.env["APPWRITE_FUNCTION_EVENT_DATA"],
"APPWRITE_FUNCTION_DATA": req.env["APPWRITE_FUNCTION_DATA"],
"APPWRITE_FUNCTION_USER_ID": req.env["APPWRITE_FUNCTION_USER_ID"],
"APPWRITE_FUNCTION_JWT": req.env["APPWRITE_FUNCTION_JWT"],
"APPWRITE_FUNCTION_PROJECT_ID": req.env["APPWRITE_FUNCTION_PROJECT_ID"],
"CUSTOM_VARIABLE": req.env["CUSTOM_VARIABLE"]
"APPWRITE_FUNCTION_ID": req.variables["APPWRITE_FUNCTION_ID"],
"APPWRITE_FUNCTION_NAME": req.variables["APPWRITE_FUNCTION_NAME"],
"APPWRITE_FUNCTION_DEPLOYMENT": req.variables["APPWRITE_FUNCTION_DEPLOYMENT"],
"APPWRITE_FUNCTION_TRIGGER": req.variables["APPWRITE_FUNCTION_TRIGGER"],
"APPWRITE_FUNCTION_RUNTIME_NAME": req.variables["APPWRITE_FUNCTION_RUNTIME_NAME"],
"APPWRITE_FUNCTION_RUNTIME_VERSION": req.variables["APPWRITE_FUNCTION_RUNTIME_VERSION"],
"APPWRITE_FUNCTION_EVENT": req.variables["APPWRITE_FUNCTION_EVENT"],
"APPWRITE_FUNCTION_EVENT_DATA": req.variables["APPWRITE_FUNCTION_EVENT_DATA"],
"APPWRITE_FUNCTION_DATA": req.variables["APPWRITE_FUNCTION_DATA"],
"APPWRITE_FUNCTION_USER_ID": req.variables["APPWRITE_FUNCTION_USER_ID"],
"APPWRITE_FUNCTION_JWT": req.variables["APPWRITE_FUNCTION_JWT"],
"APPWRITE_FUNCTION_PROJECT_ID": req.variables["APPWRITE_FUNCTION_PROJECT_ID"],
"CUSTOM_VARIABLE": req.variables["CUSTOM_VARIABLE"]
])
}

View file

@ -353,16 +353,58 @@ class AuthTest extends TestCase
'$id' => ID::custom('123'),
'memberships' => [
[
'confirm' => true,
'$id' => ID::custom('456'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'confirm' => true,
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
]
]
]);
$roles = Auth::getRoles($user);
$this->assertCount(9, $roles);
$this->assertContains(Role::users()->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'administrator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('456'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
}
public function testPrivilegedUserRoles(): void
{
Authorization::setRole(Auth::USER_ROLE_OWNER);
$user = new Document([
'$id' => ID::custom('123'),
'memberships' => [
[
'$id' => ID::custom('def'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
@ -373,42 +415,6 @@ class AuthTest extends TestCase
$roles = Auth::getRoles($user);
$this->assertCount(7, $roles);
$this->assertContains(Role::users()->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'administrator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
}
public function testPrivilegedUserRoles(): void
{
Authorization::setRole(Auth::USER_ROLE_OWNER);
$user = new Document([
'$id' => ID::custom('123'),
'memberships' => [
[
'confirm' => true,
'teamId' => ID::custom('abc'),
'roles' => [
'administrator',
'moderator'
]
],
[
'confirm' => true,
'teamId' => ID::custom('def'),
'roles' => [
'guest'
]
]
]
]);
$roles = Auth::getRoles($user);
$this->assertCount(5, $roles);
$this->assertNotContains(Role::users()->toString(), $roles);
$this->assertNotContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
@ -416,6 +422,8 @@ class AuthTest extends TestCase
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
}
public function testAppUserRoles(): void
@ -425,16 +433,18 @@ class AuthTest extends TestCase
'$id' => ID::custom('123'),
'memberships' => [
[
'confirm' => true,
'$id' => ID::custom('def'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'confirm' => true,
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
@ -444,7 +454,7 @@ class AuthTest extends TestCase
$roles = Auth::getRoles($user);
$this->assertCount(5, $roles);
$this->assertCount(7, $roles);
$this->assertNotContains(Role::users()->toString(), $roles);
$this->assertNotContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
@ -452,5 +462,7 @@ class AuthTest extends TestCase
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
}
}

View file

@ -54,8 +54,9 @@ class MessagingChannelsTest extends TestCase
'$id' => ID::custom('user' . $this->connectionsCount),
'memberships' => [
[
'confirm' => true,
'$id' => ID::custom('member' . $i),
'teamId' => ID::custom('team' . $i),
'confirm' => true,
'roles' => [
empty($index % 2)
? Auth::USER_ROLE_ADMIN
@ -122,11 +123,11 @@ class MessagingChannelsTest extends TestCase
* Check for correct amount of subscriptions:
* - XXX users
* - XXX teams
* - XXX team roles (2 roles per team)
* - XXX team roles (3 roles per team)
* - 1 guests
* - 1 users
*/
$this->assertCount(($this->connectionsAuthenticated + (3 * $this->connectionsPerChannel) + 2), $this->realtime->subscriptions['1']);
$this->assertCount(($this->connectionsAuthenticated + (4 * $this->connectionsPerChannel) + 2), $this->realtime->subscriptions['1']);
/**
* Check for connections
@ -138,7 +139,7 @@ class MessagingChannelsTest extends TestCase
$this->realtime->unsubscribe(-1);
$this->assertCount($this->connectionsTotal, $this->realtime->connections);
$this->assertCount(($this->connectionsAuthenticated + (3 * $this->connectionsPerChannel) + 2), $this->realtime->subscriptions['1']);
$this->assertCount(($this->connectionsAuthenticated + (4 * $this->connectionsPerChannel) + 2), $this->realtime->subscriptions['1']);
for ($i = 0; $i < $this->connectionsCount; $i++) {
$this->realtime->unsubscribe($i);
@ -259,6 +260,7 @@ class MessagingChannelsTest extends TestCase
for ($i = 0; $i < $this->connectionsPerChannel; $i++) {
$permissions[] = Role::team(ID::custom('team' . $i))->toString();
$permissions[] = Role::member(ID::custom('member' . $i))->toString();
}
$event = [
'project' => '1',
@ -284,13 +286,13 @@ class MessagingChannelsTest extends TestCase
$this->assertStringEndsWith($index, $receiver);
}
$role = empty($index % 2)
? Auth::USER_ROLE_ADMIN
: 'member';
$permissions = [
Role::team(
ID::custom('team' . $index),
(empty($index % 2)
? Auth::USER_ROLE_ADMIN
: 'member')
)->toString()
Role::team(ID::custom('team' . $index), $role)->toString(),
Role::member(ID::custom('member' . $index))->toString()
];
$event = [