diff --git a/CHANGES.md b/CHANGES.md index f47d2c5bf7..d11186427b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/app/config/collections.php b/app/config/collections.php index 6396e255cb..0c8108fb58 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -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], ], diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 2a2c73e3e8..b7aef1505b 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -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 diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d1edee3e38..db8b15e996 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -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); diff --git a/app/executor.php b/app/executor.php index d3ef55d9ae..fba8c4c416 100644 --- a/app/executor.php +++ b/app/executor.php @@ -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 */ diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index 27a959ece4..42b5ed00dc 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -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) ]); diff --git a/app/views/console/comps/permissions-matrix.phtml b/app/views/console/comps/permissions-matrix.phtml index 6a688d0629..7dd80c19cd 100644 --- a/app/views/console/comps/permissions-matrix.phtml +++ b/app/views/console/comps/permissions-matrix.phtml @@ -46,8 +46,20 @@ $escapedPermissions = \array_map(function ($perm) { :value="rawPermissions"/> - - + + + +
+ + @@ -60,7 +72,16 @@ $escapedPermissions = \array_map(function ($perm) { - .window="addPermission('', role, { })"> - - - - - - diff --git a/app/views/console/databases/collection.phtml b/app/views/console/databases/collection.phtml index 6c844875e0..19454a88db 100644 --- a/app/views/console/databases/collection.phtml +++ b/app/views/console/databases/collection.phtml @@ -508,7 +508,7 @@ $permissions = $this->getParam('permissions', null);
  • Settings

    -
    +
    @@ -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"> diff --git a/app/views/console/functions/function.phtml b/app/views/console/functions/function.phtml index d6290986e8..46ac7a4db8 100644 --- a/app/views/console/functions/function.phtml +++ b/app/views/console/functions/function.phtml @@ -416,7 +416,7 @@ sort($patterns);
  • Role
    - - - - - - -
    - +
    - + - diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 42ead45a26..f932ba4bc3 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -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']); } diff --git a/app/workers/functions.php b/app/workers/functions.php index bcc8cb8dd3..b7ef80a40b 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -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(); diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index a04b25270b..39f44c1d91 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -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 diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index ae0022f7b2..3424e8d19f 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -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 diff --git a/docs/tutorials/add-route.md b/docs/tutorials/add-route.md new file mode 100644 index 0000000000..6b1bf7e7cf --- /dev/null +++ b/docs/tutorials/add-route.md @@ -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... +}); +``` diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index f463538519..19a8d73470 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -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];} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 0c79e5ba70..1ce692ef9f 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -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];} diff --git a/public/scripts/filters.js b/public/scripts/filters.js index b913a5648f..3e6679285f 100644 --- a/public/scripts/filters.js +++ b/public/scripts/filters.js @@ -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"; diff --git a/public/scripts/permissions-matrix.js b/public/scripts/permissions-matrix.js index a4d8cc53e9..81a869a8f4 100644 --- a/public/scripts/permissions-matrix.js +++ b/public/scripts/permissions-matrix.js @@ -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(); } })); }); diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 37a2ea8d39..c290d19862 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -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(); + } } } } diff --git a/src/Appwrite/Usage/Calculators/TimeSeries.php b/src/Appwrite/Usage/Calculators/TimeSeries.php index c2a6b4a141..01c8661206 100644 --- a/src/Appwrite/Usage/Calculators/TimeSeries.php +++ b/src/Appwrite/Usage/Calculators/TimeSeries.php @@ -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 diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php index 74996936b9..2bfe46e28d 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php @@ -8,7 +8,7 @@ class Executions extends Base 'trigger', 'status', 'statusCode', - 'time' + 'duration' ]; /** diff --git a/src/Appwrite/Utopia/Response/Model/Build.php b/src/Appwrite/Utopia/Response/Model/Build.php index 0a28cbffa3..b76f0ee083 100644 --- a/src/Appwrite/Utopia/Response/Model/Build.php +++ b/src/Appwrite/Utopia/Response/Model/Build.php @@ -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, ]) diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index 74497cf935..13011a24b7 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -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, ]) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index cde5dd3e29..5126b8eb8d 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -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); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2d037abbf2..94f7e84f43 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -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'); diff --git a/tests/resources/functions/dart/main.dart b/tests/resources/functions/dart/main.dart index 93463e9321..0a6cabdac7 100644 --- a/tests/resources/functions/dart/main.dart +++ b/tests/resources/functions/dart/main.dart @@ -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 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'] }); } \ No newline at end of file diff --git a/tests/resources/functions/node/index.js b/tests/resources/functions/node/index.js index 67e011e77e..7f190bcaf5 100644 --- a/tests/resources/functions/node/index.js +++ b/tests/resources/functions/node/index.js @@ -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 }); } \ No newline at end of file diff --git a/tests/resources/functions/php-fn/index.php b/tests/resources/functions/php-fn/index.php index e3ce05829c..7947c4a6a6 100644 --- a/tests/resources/functions/php-fn/index.php +++ b/tests/resources/functions/php-fn/index.php @@ -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'], ]); }; diff --git a/tests/resources/functions/php-large/index.php b/tests/resources/functions/php-large/index.php index 8bd0de5e60..84b6a991fe 100644 --- a/tests/resources/functions/php-large/index.php +++ b/tests/resources/functions/php-large/index.php @@ -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' => "êä" ]); }; diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php index 8bd0de5e60..84b6a991fe 100644 --- a/tests/resources/functions/php/index.php +++ b/tests/resources/functions/php/index.php @@ -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' => "êä" ]); }; diff --git a/tests/resources/functions/python/main.py b/tests/resources/functions/python/main.py index af7d79ab23..ff78d6d336 100644 --- a/tests/resources/functions/python/main.py +++ b/tests/resources/functions/python/main.py @@ -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'], }) \ No newline at end of file diff --git a/tests/resources/functions/ruby/main.rb b/tests/resources/functions/ruby/main.rb index 41b9b0fc52..d6ccdfb073 100644 --- a/tests/resources/functions/ruby/main.rb +++ b/tests/resources/functions/ruby/main.rb @@ -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 \ No newline at end of file diff --git a/tests/resources/functions/swift/index.swift b/tests/resources/functions/swift/index.swift index 67b0f35bec..b3dcdbc91a 100644 --- a/tests/resources/functions/swift/index.swift +++ b/tests/resources/functions/swift/index.swift @@ -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"] ]) } \ No newline at end of file diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 4cd180b956..1480fbb23f 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -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); } } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index 26a1106688..dd2ec04401 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -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 = [