1
0
Fork 0
mirror of synced 2024-05-20 20:52:36 +12:00

Merge branch '0.16.x' into feat-response-filters-0.16.x

This commit is contained in:
Steven Nguyen 2022-09-12 22:21:52 +00:00
commit 7f0d4e6c36
61 changed files with 929 additions and 297 deletions

View file

@ -2,33 +2,19 @@ name: "Linter"
on: [pull_request]
jobs:
tests:
lint:
name: Linter
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
- name: Install dependencies
uses: php-actions/composer@v6
with:
php_version: '8.0'
args: --profile --ignore-platform-reqs
- name: Run Linter
run: ./vendor/bin/phpcs -p
run: |
docker run --rm -v $PWD:/app composer sh -c \
"composer install --profile --ignore-platform-reqs && composer lint"

View file

@ -1,3 +1,37 @@
# Version 1.0.0-RC1
# DO NOT INSTALL THIS VERSION ON ANY EXISTING INSTANCES OF APPWRITE, THIS IS A TEST RELEASE AND DOES NOT HAVE BACKWARDS COMPATIBILITY!!
## BREAKING CHANGES
- All Date values are now stored as ISO-8601 instead of UNIX timestamps [#3516](https://github.com/appwrite/appwrite/pull/3516)
- Permission levels and syntax have been reworked. See the Permissions V2 section in the document for more information [#3700](https://github.com/appwrite/appwrite/pull/3700)
- Function Variables are now stored in a seperate collection with their own API endpoints [#3634](https://github.com/appwrite/appwrite/pull/3634)
- Resources that are computed asynchronously, such as function deployments, will now return a `202 Accepted` status code instead of `200 OK` [#3547](https://github.com/appwrite/appwrite/pull/3547)
- Queries have been improved to allow even more flexibility, and introduced to new endpoints. See the Queries V2 section in the document for more information [#3702](https://github.com/appwrite/appwrite/pull/3702)
- 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)
- `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)
- Added automatic cache cleaning for internal Appwrite services [#3491](https://github.com/appwrite/appwrite/pull/3491)
- Added the ability for Appwrite to handle importing hashed passwords, this can be leveraged to import existing user data from other systems. More information can be found in the document linked above. [#2747](https://github.com/appwrite/appwrite/pull/2747)
- `Users` has now been renamed to `Authentication` within the Appwrite console [#3664](https://github.com/appwrite/appwrite/pull/3664)
- More endpoints were made public (for guests) with proper rate limits [#3741](https://github.com/appwrite/appwrite/pull/3741)
- Added Disqus, Podio, and Etsy OAuth providers [#3526](https://github.com/appwrite/appwrite/pull/3526), [#3488](https://github.com/appwrite/appwrite/pull/3488), [#3522](https://github.com/appwrite/appwrite/pull/3522)
- Function logs now capture stdout [#3656](https://github.com/appwrite/appwrite/pull/3656)
- Added the ability to grant guests write permissions for documents, files and executions [#3727](https://github.com/appwrite/appwrite/pull/3727)
## Bugs
- Fixed an issue where after resetting your password in the Appwrite console, you would not be redirected to the login page. [#3654](https://github.com/appwrite/appwrite/pull/3654)
- Fixed an issue where invalid data could be loaded into the Appwrite console. [#3660](https://github.com/appwrite/appwrite/pull/3660)
- Fixed an issue where users using the MySQL adapter for Appwrite would run into an issue with full text indexes [#154](https://github.com/utopia-php/database/pull/154)
- Fix teams being created with no owners [#3558](https://github.com/appwrite/appwrite/pull/3558)
- Fixed a bug where you could not search users by phone [#3619](https://github.com/appwrite/appwrite/pull/3619)
- Fixed a bug where unaccepted invitations would grant access to projects [#3738](https://github.com/appwrite/appwrite/pull/3738)
For more information on the changes, please see our [1.0-RC1 Documentation](https://warm-tray-285.notion.site/Appwrite-1-0-0-RC1-a5eab87e115d454db0259f707fc88535).
# Version 0.15.3
## Features
- Added hint during Installation for DNS Configuration by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/2450

View file

@ -2673,7 +2673,7 @@ $collections = [
'filters' => [],
],
[
'$id' => ID::custom('time'),
'$id' => ID::custom('duration'),
'type' => Database::VAR_FLOAT,
'format' => '',
'size' => 0,
@ -2732,9 +2732,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

@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '9.1.0-RC1',
'version' => '10.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@ -63,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '8.0.0-dev.1',
'version' => '8.0.0-dev.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@ -81,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '0.7.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '0.8.0-SNAPSHOT',
'version' => '1.0.0-SNAPSHOT',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@ -162,7 +162,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '6.1.0-RC1',
'version' => '7.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@ -180,7 +180,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '0.19.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@ -208,7 +208,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '7.1.0-RC1',
'version' => '8.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@ -226,7 +226,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '5.1.0-RC1',
'version' => '6.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@ -244,7 +244,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '6.1.0-RC1',
'version' => '7.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@ -262,7 +262,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '0.11.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@ -280,7 +280,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '6.1.0-RC1',
'version' => '7.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@ -298,7 +298,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '0.2.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => '',
'enabled' => false,
@ -316,7 +316,7 @@ return [
[
'key' => 'java',
'name' => 'Java',
'version' => '0.0.3-SNAPSHOT',
'version' => '1.0.0-SNAPSHOT',
'url' => 'https://github.com/appwrite/sdk-for-java',
'package' => '',
'enabled' => false,
@ -334,7 +334,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.5.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => false,
@ -352,7 +352,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '7.0.0-dev.1',
'version' => '7.0.0-dev.2',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@ -370,7 +370,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '0.7.0-SNAPSHOT',
'version' => '1.0.0-SNAPSHOT',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@ -392,7 +392,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '0.7.0-RC1',
'version' => '1.0.0-RC2',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -134,8 +134,9 @@ App::post('/v1/account')
$events->setParam('userId', $user->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_ACCOUNT);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_ACCOUNT);
});
App::post('/v1/account/sessions/email')
@ -1250,8 +1251,9 @@ App::post('/v1/account/jwt')
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic(new Document(['jwt' => $jwt->encode([
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => $jwt->encode([
// 'uid' => 1,
// 'aud' => 'http://site.com',
// 'scopes' => ['user'],
@ -1979,8 +1981,9 @@ App::post('/v1/account/recovery')
// Hide secret for clients
$recovery->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : '');
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($recovery, Response::MODEL_TOKEN);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($recovery, Response::MODEL_TOKEN);
});
App::put('/v1/account/recovery')
@ -2135,8 +2138,9 @@ App::post('/v1/account/verification')
// Hide secret for clients
$verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : '');
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($verification, Response::MODEL_TOKEN);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($verification, Response::MODEL_TOKEN);
});
App::put('/v1/account/verification')
@ -2276,8 +2280,9 @@ App::post('/v1/account/verification/phone')
// Hide secret for clients
$verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : '');
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($verification, Response::MODEL_TOKEN);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($verification, Response::MODEL_TOKEN);
});
App::put('/v1/account/verification/phone')

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

@ -217,8 +217,9 @@ App::post('/v1/databases')
$events->setParam('databaseId', $database->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($database, Response::MODEL_DATABASE);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($database, Response::MODEL_DATABASE);
});
App::get('/v1/databases')
@ -529,8 +530,9 @@ App::post('/v1/databases/:databaseId/collections')
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($collection, Response::MODEL_COLLECTION);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($collection, Response::MODEL_COLLECTION);
});
App::get('/v1/databases/:databaseId/collections')
@ -887,8 +889,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
'array' => $array,
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
@ -929,8 +932,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
'format' => APP_DATABASE_ATTRIBUTE_EMAIL,
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
@ -987,8 +991,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
'formatOptions' => ['elements' => $elements],
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
@ -1029,8 +1034,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
'format' => APP_DATABASE_ATTRIBUTE_IP,
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
@ -1071,8 +1077,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
'format' => APP_DATABASE_ATTRIBUTE_URL,
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
@ -1142,8 +1149,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
$attribute->setAttribute('max', \intval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
@ -1216,8 +1224,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
$attribute->setAttribute('max', \floatval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
@ -1257,8 +1266,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
'array' => $array,
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
});
@ -1300,8 +1310,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
'filters' => ['datetime']
]), $response, $dbForProject, $database, $events);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
});
@ -1557,7 +1568,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$oldAttributes[] = [
'key' => '$id',
'type' => 'string',
'type' => Database::VAR_STRING,
'status' => 'available',
'required' => true,
'array' => false,
@ -1567,7 +1578,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$oldAttributes[] = [
'key' => '$createdAt',
'type' => 'string',
'type' => Database::VAR_DATETIME,
'status' => 'available',
'signed' => false,
'required' => false,
@ -1578,7 +1589,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$oldAttributes[] = [
'key' => '$updatedAt',
'type' => 'string',
'type' => Database::VAR_DATETIME,
'status' => 'available',
'signed' => false,
'required' => false,
@ -1642,12 +1653,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('indexId', $index->getId())
->setContext('collection', $collection)
->setContext('database', $db)
->setContext('collection', $collection)
->setContext('database', $db)
;
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($index, Response::MODEL_INDEX);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($index, Response::MODEL_INDEX);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
@ -1827,7 +1839,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->param('documentId', '', new CustomId(), 'Document ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
@ -1925,8 +1937,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->setContext('database', $database)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($document, Response::MODEL_DOCUMENT);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@ -2207,7 +2220,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->param('collectionId', null, new UID(), 'Collection ID.')
->param('documentId', null, new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('events')

View file

@ -90,8 +90,9 @@ App::post('/v1/functions')
$eventsInstance->setParam('functionId', $function->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($function, Response::MODEL_FUNCTION);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($function, Response::MODEL_FUNCTION);
});
App::get('/v1/functions')
@ -754,8 +755,9 @@ App::post('/v1/functions/:functionId/deployments')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
App::get('/v1/functions/:functionId/deployments')
@ -936,7 +938,7 @@ App::post('/v1/functions/:functionId/executions')
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->param('functionId', '', new UID(), 'Function ID.')
->param('data', '', new Text(8192), 'String of custom data to send to function.', true)
->param('async', true, new Boolean(), 'Execute code asynchronously. Default value is true.', true)
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
@ -998,7 +1000,7 @@ App::post('/v1/functions/:functionId/executions')
'statusCode' => 0,
'response' => '',
'stderr' => '',
'time' => 0.0,
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
])));
@ -1041,9 +1043,9 @@ App::post('/v1/functions/:functionId/executions')
$event->trigger();
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
return $response->dynamic($execution, Response::MODEL_EXECUTION);
return $response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($execution, Response::MODEL_EXECUTION);
}
$vars = array_reduce($function['vars'] ?? [], function (array $carry, Document $var) {
@ -1085,11 +1087,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());
@ -1103,11 +1105,12 @@ 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);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
@ -1339,8 +1342,9 @@ App::post('/v1/functions/:functionId/variables')
$dbForProject->deleteCachedDocument('functions', $function->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($variable, Response::MODEL_VARIABLE);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
});
App::get('/v1/functions/:functionId/variables')

View file

@ -167,8 +167,9 @@ App::post('/v1/projects')
$dbForProject->createCollection($key, $attributes, $indexes);
}
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($project, Response::MODEL_PROJECT);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($project, Response::MODEL_PROJECT);
});
App::get('/v1/projects')
@ -624,8 +625,9 @@ App::post('/v1/projects/:projectId/webhooks')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($webhook, Response::MODEL_WEBHOOK);
});
App::get('/v1/projects/:projectId/webhooks')
@ -871,8 +873,9 @@ App::post('/v1/projects/:projectId/keys')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($key, Response::MODEL_KEY);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($key, Response::MODEL_KEY);
});
App::get('/v1/projects/:projectId/keys')
@ -1072,8 +1075,9 @@ App::post('/v1/projects/:projectId/platforms')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($platform, Response::MODEL_PLATFORM);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($platform, Response::MODEL_PLATFORM);
});
App::get('/v1/projects/:projectId/platforms')
@ -1289,8 +1293,9 @@ App::post('/v1/projects/:projectId/domains')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($domain, Response::MODEL_DOMAIN);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($domain, Response::MODEL_DOMAIN);
});
App::get('/v1/projects/:projectId/domains')

View file

@ -48,7 +48,7 @@ App::post('/v1/storage/buckets')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
->label('event', 'buckets.[bucketId].create')
->label('audits.resource', 'buckets/{response.$id}')
->label('audits.resource', 'bucket/{response.$id}')
->label('usage.metric', 'buckets.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
@ -136,8 +136,9 @@ App::post('/v1/storage/buckets')
->setParam('bucketId', $bucket->getId())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($bucket, Response::MODEL_BUCKET);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($bucket, Response::MODEL_BUCKET);
});
App::get('/v1/storage/buckets')
@ -218,7 +219,7 @@ App::put('/v1/storage/buckets/:bucketId')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
->label('event', 'buckets.[bucketId].update')
->label('audits.resource', 'buckets/{response.$id}')
->label('audits.resource', 'bucket/{response.$id}')
->label('usage.metric', 'buckets.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
@ -284,7 +285,7 @@ App::delete('/v1/storage/buckets/:bucketId')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
->label('event', 'buckets.[bucketId].delete')
->label('audits.resource', 'buckets/{request.bucketId}')
->label('audits.resource', 'bucket/{request.bucketId}')
->label('usage.metric', 'buckets.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
@ -326,7 +327,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'buckets.[bucketId].files.[fileId].create')
->label('audits.resource', 'files/{response.$id}')
->label('audits.resource', 'file/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.create')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
@ -344,7 +345,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('file', [], new File(), 'Binary file.', false)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@ -643,8 +644,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
$metadata = null; // was causing leaks as it was passed by reference
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($file, Response::MODEL_FILE);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($file, Response::MODEL_FILE);
});
App::get('/v1/storage/buckets/:bucketId/files')
@ -1236,7 +1238,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'buckets.[bucketId].files.[fileId].update')
->label('audits.resource', 'files/{response.$id}')
->label('audits.resource', 'file/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.update')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
@ -1251,7 +1253,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')

View file

@ -116,8 +116,9 @@ App::post('/v1/teams')
$events->setParam('userId', $user->getId());
}
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($team, Response::MODEL_TEAM);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($team, Response::MODEL_TEAM);
});
App::get('/v1/teams')
@ -446,14 +447,15 @@ App::post('/v1/teams/:teamId/memberships')
->setParam('membershipId', $membership->getId())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $invitee->getAttribute('name'))
->setAttribute('userEmail', $invitee->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $invitee->getAttribute('name'))
->setAttribute('userEmail', $invitee->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
});
App::get('/v1/teams/:teamId/memberships')

View file

@ -108,8 +108,9 @@ App::post('/v1/users')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/bcrypt')
@ -136,8 +137,9 @@ App::post('/v1/users/bcrypt')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/md5')
@ -164,8 +166,9 @@ App::post('/v1/users/md5')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/argon2')
@ -192,8 +195,9 @@ App::post('/v1/users/argon2')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/sha')
@ -227,8 +231,9 @@ App::post('/v1/users/sha')
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/phpass')
@ -255,8 +260,9 @@ App::post('/v1/users/phpass')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt')
@ -296,8 +302,9 @@ App::post('/v1/users/scrypt')
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt-modified')
@ -327,8 +334,9 @@ App::post('/v1/users/scrypt-modified')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
});
App::get('/v1/users')

View file

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

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

View file

@ -413,7 +413,7 @@ $permissions = $this->getParam('permissions', null);
</div>
</li>
<?php if(!$new): ?>
<li data-state="/console/databases/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
<li data-state="/console/databases/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}&databaseId={{router.params.databaseId}}">
<h2>Activity</h2>
<?php echo $logs->render(); ?>

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();

40
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6194919aa35d896dc3dfdb846936cba4",
"content-hash": "aca68a1c1c7d762cabd02ce02cf40df4",
"packages": [
{
"name": "adhocore/jwt",
@ -1591,25 +1591,25 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.5.2",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1638,7 +1638,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
},
"funding": [
{
@ -1654,7 +1654,7 @@
"type": "tidelift"
}
],
"time": "2022-01-02T09:53:40+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/polyfill-php80",
@ -4812,16 +4812,16 @@
},
{
"name": "sebastian/type",
"version": "3.1.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "fb44e1cc6e557418387ad815780360057e40753e"
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e",
"reference": "fb44e1cc6e557418387ad815780360057e40753e",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"shasum": ""
},
"require": {
@ -4833,7 +4833,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@ -4856,7 +4856,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/3.1.0"
"source": "https://github.com/sebastianbergmann/type/tree/3.2.0"
},
"funding": [
{
@ -4864,7 +4864,7 @@
"type": "github"
}
],
"time": "2022-08-29T06:55:37+00:00"
"time": "2022-09-12T14:47:03+00:00"
},
{
"name": "sebastian/version",
@ -5382,13 +5382,7 @@
"ext-fileinfo": "*"
},
"platform-overrides": {
"php": "8.0",
"ext-imagick": "3.7.0",
"ext-yaml": "2.2.2",
"ext-redis": "5.3.7",
"ext-swoole": "4.8.10",
"ext-zstd": "0.11.0",
"ext-mongodb": "1.13.0"
"php": "8.0"
},
"plugin-api-version": "2.2.0"
}

View file

@ -3,4 +3,3 @@ appwrite avatars getInitials \

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";}

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";}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

@ -424,7 +424,7 @@ fieldset {
width: ~"calc(100% - 2px)";
max-height: 180px;
visibility: visible !important;
object-fit: contain;
object-fit: cover;
}
.video-preview {

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

@ -36,9 +36,9 @@ class Bucket extends Model
'array' => true,
])
->addRule('fileSecurity', [
'type' => self::TYPE_STRING,
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether file-level security is enabled. [Learn more about permissions](/docs/permissions).',
'default' => '',
'default' => false,
'example' => true,
])
->addRule('name', [

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

@ -1185,6 +1185,7 @@ trait AccountBase
/**
* @depends testCreateAccountRecovery
*/
#[Retry(count: 1)]
public function testUpdateAccountRecovery($data): array
{
$id = $data['id'] ?? '';

View file

@ -16,6 +16,94 @@ class DatabasesCustomClientTest extends Scope
use ProjectCustom;
use SideClient;
public function testAllowedPermissions(): void
{
/**
* Test for SUCCESS
*/
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Test Database'
]);
$databaseId = $database['body']['$id'];
// Collection aliases write to create, update, delete
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Movies',
'documentSecurity' => true,
'permissions' => [
Permission::write(Role::user($this->getUser()['$id'])),
],
]);
$moviesId = $movies['body']['$id'];
$this->assertContains(Permission::create(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'title',
'size' => 256,
'required' => true,
]);
sleep(1);
// Document aliases write to update, delete
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'title' => 'Captain America',
],
'permissions' => [
Permission::write(Role::user($this->getUser()['$id'])),
]
]);
$this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
/**
* Test for FAILURE
*/
// Document does not allow create permission
$document2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'title' => 'Captain America',
],
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(400, $document2['headers']['status-code']);
}
public function testUpdateWithoutPermission(): array
{
// If document has been created by server and client tried to update it without adjusting permissions, permission validation should be skipped

View file

@ -225,4 +225,54 @@ class DatabasesPermissionsGuestTest extends Scope
Authorization::setRole($role);
}
}
public function testWriteDocumentWithPermissions()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'databaseId' => ID::unique(),
'name' => 'GuestPermissionsWrite',
]);
$this->assertEquals(201, $database['headers']['status-code']);
$this->assertEquals('GuestPermissionsWrite', $database['body']['name']);
$databaseId = $database['body']['$id'];
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
'collectionId' => ID::unique(),
'name' => 'Movies',
'permissions' => [
Permission::create(Role::any()),
],
'documentSecurity' => true
]);
$moviesId = $movies['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/attributes/string', $this->getServerHeader(), [
'key' => 'title',
'size' => 256,
'required' => true,
]);
sleep(1);
$document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'documentId' => ID::unique(),
'data' => [
'title' => 'Thor: Ragnarok',
],
'permissions' => [
Permission::read(Role::any()),
]
]);
$this->assertEquals(201, $document['headers']['status-code']);
$this->assertEquals('Thor: Ragnarok', $document['body']['title']);
}
}

View file

@ -245,6 +245,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$this->assertEquals(202, $execution['headers']['status-code']);
@ -343,6 +344,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
], [
'data' => 'foobar',
'async' => true,
]);
$this->assertEquals(202, $execution['headers']['status-code']);
@ -388,6 +390,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$this->assertEquals(202, $execution['headers']['status-code']);
@ -568,7 +571,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'data' => 'foobar',
'async' => false
// Testing default value, should be 'async' => false
]);
$output = json_decode($execution['body']['response'], true);

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
@ -727,6 +727,7 @@ class FunctionsCustomServerTest extends Scope
/**
* @depends testUpdateDeployment
*/
#[Retry(count: 2)]
public function testSyncCreateExecution($data): array
{
/**
@ -737,7 +738,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => false,
// Testing default value, should be 'async' => false
]);
$this->assertEquals(201, $execution['headers']['status-code']);
@ -749,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;
}
@ -908,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');
@ -978,6 +979,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';
@ -1092,6 +1094,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';
@ -1204,6 +1207,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';
@ -1317,6 +1321,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';
@ -1430,6 +1435,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';

View file

@ -1122,7 +1122,6 @@ class RealtimeCustomClientTest extends Scope
], $this->getHeaders()), [
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
@ -1263,7 +1262,9 @@ class RealtimeCustomClientTest extends Scope
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), []);
], $this->getHeaders()), [
'async' => true
]);
$this->assertEquals($execution['headers']['status-code'], 202);
$this->assertNotEmpty($execution['body']['$id']);

View file

@ -1065,6 +1065,68 @@ class StorageCustomClientTest extends Scope
$this->assertEmpty($file['body']);
}
public function testAllowedPermissions(): void
{
/**
* Test for SUCCESS
*/
// Bucket aliases write to create, update, delete
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => ID::unique(),
'name' => 'Test Bucket',
'permissions' => [
Permission::write(Role::user($this->getUser()['$id'])),
],
'fileSecurity' => true,
]);
$bucketId = $bucket['body']['$id'];
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertContains(Permission::create(Role::user($this->getUser()['$id'])), $bucket['body']['$permissions']);
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $bucket['body']['$permissions']);
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $bucket['body']['$permissions']);
// File aliases write to update, delete
$file1 = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
'permissions' => [
Permission::write(Role::user($this->getUser()['$id'])),
]
]);
$this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $file1['body']['$permissions']);
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $file1['body']['$permissions']);
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $file1['body']['$permissions']);
/**
* Test for FAILURE
*/
// File does not allow create permission
$file2 = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(400, $file2['headers']['status-code']);
}
public function testCreateFileDefaultPermissions(): array
{
/**
@ -1220,9 +1282,8 @@ class StorageCustomClientTest extends Scope
], $this->getHeaders()), [
'permissions' => [
Permission::read(Role::user(ID::custom('notme'))),
Permission::create(Role::user(ID::custom('notme'))),
Permission::update(Role::user(ID::custom('notme'))),
Permission::delete(Role::user(ID::custom('notme'))),
Permission::update(Role::user(ID::custom('notme'))),
Permission::delete(Role::user(ID::custom('notme'))),
],
]);

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Users;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Utopia\Database\ID;
@ -852,6 +853,7 @@ trait UsersBase
/**
* @depends testGetUser
*/
#[Retry(count: 1)]
public function testUpdateUserStatus(array $data): array
{
/**

View file

@ -556,7 +556,6 @@ trait WebhooksBase
], $this->getHeaders()), [
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Webhooks;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
@ -416,6 +417,7 @@ class WebhooksCustomClientTest extends Scope
/**
* @depends testDeleteAccountSessions
*/
#[Retry(count: 1)]
public function testUpdateAccountName($data): array
{
$id = $data['id'] ?? '';

View file

@ -605,7 +605,9 @@ class WebhooksCustomServerTest extends Scope
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $id . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
], $this->getHeaders()), [
'async' => true
]);
$executionId = $execution['body']['$id'] ?? '';

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 = [