diff --git a/README.md b/README.md index e4c8b4ca3b..d7a45ebcb6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Using Appwrite, you can easily integrate your app with user authentication & mul


- Appwrite - 100% open source alternative for Firebase | Product Hunt + Appwrite - 100% open source alternative for Firebase | Product Hunt

diff --git a/app/config/platforms.php b/app/config/platforms.php index 8755f46144..828e4d3245 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -15,7 +15,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '10.0.0', + 'version' => '10.1.0', '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', + 'version' => '8.1.0', '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' => '1.0.0', + 'version' => '1.1.0', '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' => '1.0.0', + 'version' => '1.1.0', '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' => '7.0.0', + 'version' => '7.1.0', 'url' => 'https://github.com/appwrite/sdk-for-console', 'package' => '', 'enabled' => true, @@ -180,7 +180,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '1.0.0', + 'version' => '1.1.1', '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' => '8.0.0', + 'version' => '8.1.0', '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' => '6.0.0', + 'version' => '6.1.0', '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' => '7.0.0', + 'version' => '7.1.0', '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' => '1.0.0', + 'version' => '1.1.0', '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' => '7.0.0', + 'version' => '7.1.0', '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' => '1.0.0', + 'version' => '1.1.0', 'url' => 'https://github.com/appwrite/sdk-for-go', 'package' => '', 'enabled' => false, @@ -316,7 +316,7 @@ return [ [ 'key' => 'java', 'name' => 'Java', - 'version' => '1.0.0', + 'version' => '1.1.0', 'url' => 'https://github.com/appwrite/sdk-for-java', 'package' => '', 'enabled' => false, @@ -334,7 +334,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '1.0.0', + 'version' => '1.1.0', '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', + 'version' => '7.1.0', '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' => '1.0.0', + 'version' => '1.1.0', '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' => '1.0.0', + 'version' => '1.1.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/app/config/variables.php b/app/config/variables.php index 3eb11ad620..9f3bc018e8 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -413,7 +413,7 @@ return [ 'variables' => [ [ 'name' => '_APP_SMS_PROVIDER', - 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.", + 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.", 'introduction' => '0.15.0', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ef94b251c8..0cd1c59b77 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1418,7 +1418,7 @@ App::get('/v1/account/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SESSION) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to get the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.') ->inject('response') ->inject('user') ->inject('locale') @@ -1696,7 +1696,7 @@ App::delete('/v1/account/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->label('abuse-limit', 100) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to delete the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.') ->inject('request') ->inject('response') ->inject('user') @@ -1769,7 +1769,7 @@ App::patch('/v1/account/sessions/:sessionId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SESSION) ->label('abuse-limit', 10) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to update the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to update the current device session.') ->inject('request') ->inject('response') ->inject('user') diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index e2acb30772..a66df37124 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1783,7 +1783,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('databaseId', '', new UID(), 'Database ID.') - ->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).') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') @@ -1856,7 +1856,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') ->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('collectionId', '', 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, 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') @@ -2074,8 +2074,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') - ->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).') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('documentId', '', new UID(), 'Document ID.') ->inject('response') ->inject('dbForProject') ->inject('mode') @@ -2137,7 +2137,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('documentId', '', new UID(), 'Document ID.') ->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) ->inject('response') ->inject('dbForProject') @@ -2243,8 +2243,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', null, new UID(), 'Collection ID.') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID.') + ->param('documentId', '', 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, [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') @@ -2376,8 +2376,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('databaseId', '', new UID(), 'Database ID.') - ->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).') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('documentId', '', new UID(), 'Document ID.') ->inject('response') ->inject('dbForProject') ->inject('events') @@ -2534,7 +2534,7 @@ App::get('/v1/databases/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -2648,7 +2648,7 @@ App::get('/v1/databases/:databaseId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -2763,7 +2763,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f42d1059e0..ca2f237f14 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -281,7 +281,7 @@ App::get('/v1/functions/:functionId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -384,7 +384,7 @@ App::get('/v1/functions/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -1332,7 +1332,7 @@ App::post('/v1/functions/:functionId/variables') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false) ->inject('response') @@ -1384,7 +1384,7 @@ App::get('/v1/functions/:functionId/variables') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE_LIST) - ->param('functionId', null, new UID(), 'Function unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, Response $response, Database $dbForProject) { @@ -1411,8 +1411,8 @@ App::get('/v1/functions/:functionId/variables/:variableId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) { @@ -1447,8 +1447,8 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true) ->inject('response') @@ -1499,8 +1499,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->label('sdk.description', '/docs/references/functions/delete-variable.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 5c8ba8a268..bfea863d29 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -330,7 +330,7 @@ App::get('/v1/projects/:projectId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -584,7 +584,7 @@ App::post('/v1/projects/:projectId/webhooks') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') @@ -672,8 +672,8 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -706,8 +706,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') @@ -760,8 +760,8 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -798,8 +798,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.method', 'deleteWebhook') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -838,7 +838,7 @@ App::post('/v1/projects/:projectId/keys') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true) @@ -888,7 +888,7 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY_LIST) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { @@ -920,8 +920,8 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { @@ -954,8 +954,8 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true) @@ -1000,8 +1000,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->label('sdk.method', 'deleteKey') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { @@ -1040,7 +1040,7 @@ App::post('/v1/projects/:projectId/platforms') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.') ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.') ->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true) @@ -1122,8 +1122,8 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { @@ -1156,8 +1156,8 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.') ->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true) ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) @@ -1203,8 +1203,8 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.method', 'deletePlatform') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { @@ -1243,7 +1243,7 @@ App::post('/v1/projects/:projectId/domains') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('domain', null, new DomainValidator(), 'Domain name.') ->inject('response') ->inject('dbForConsole') @@ -1340,8 +1340,8 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { @@ -1374,8 +1374,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { @@ -1433,8 +1433,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->label('sdk.method', 'deleteDomain') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->inject('deletes') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1134887fb6..f236285749 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -346,7 +346,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->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('bucketId', '', 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, 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) @@ -667,7 +667,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE_LIST) - ->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('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') @@ -744,7 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->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('bucketId', '', 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 ID.') ->inject('response') ->inject('dbForProject') @@ -793,7 +793,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE) ->label('sdk.methodType', 'location') - ->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('bucketId', '', 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 ID') ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) @@ -959,7 +959,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') - ->param('bucketId', null, new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->inject('request') ->inject('response') @@ -1099,7 +1099,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') - ->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('bucketId', '', 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 ID.') ->inject('response') ->inject('request') @@ -1256,7 +1256,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->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('bucketId', '', 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, [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') @@ -1358,7 +1358,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.description', '/docs/references/storage/delete-file.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->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('bucketId', '', 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 ID.') ->inject('response') ->inject('dbForProject') @@ -1518,7 +1518,7 @@ App::get('/v1/storage/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -1629,7 +1629,7 @@ App::get('/v1/storage/:bucketId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 801c647a29..a42b9d317e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -865,7 +865,7 @@ App::get('/v1/teams/:teamId/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) - ->param('teamId', null, new UID(), 'Team ID.') + ->param('teamId', '', new UID(), 'Team ID.') ->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) ->inject('response') ->inject('dbForProject') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c83f03d96e..d2b303c6a1 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -981,7 +981,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('userId', '', new UID(), 'User ID.') - ->param('sessionId', null, new UID(), 'Session ID.') + ->param('sessionId', '', new UID(), 'Session ID.') ->inject('response') ->inject('dbForProject') ->inject('events') @@ -1176,7 +1176,7 @@ App::get('/v1/users/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/views/console/comps/permissions-matrix.phtml b/app/views/console/comps/permissions-matrix.phtml index 836b0843ad..e912f26629 100644 --- a/app/views/console/comps/permissions-matrix.phtml +++ b/app/views/console/comps/permissions-matrix.phtml @@ -81,6 +81,8 @@ $escapedPermissions = \array_map(function ($perm) { list="types" type="text" x-model="permission.role" + @keydown.enter="prevent($event)" + @keydown="clearPermission(index)" @keyup="updatePermission(index)"/> diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 2ec8199404..19ab56c69a 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -410,6 +410,14 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); $functionId = $document->getId(); + /** + * Delete Variables + */ + Console::info("Deleting variables for function " . $functionId); + $this->deleteByGroup('variables', [ + Query::equal('functionId', [$functionId]) + ], $dbForProject); + /** * Delete Deployments */ diff --git a/app/workers/messaging.php b/app/workers/messaging.php index 1fc5deaf44..e8261f7030 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -1,6 +1,5 @@ {let{type,role}=this.parsePer if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);} if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;} this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;} -const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} +const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';} return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');} return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 40edce16e7..bbdda3c914 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -607,7 +607,7 @@ this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePer if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);} if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;} this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;} -const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} +const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';} return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');} return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} diff --git a/public/scripts/permissions-matrix.js b/public/scripts/permissions-matrix.js index 81a869a8f4..5196e1cbc1 100644 --- a/public/scripts/permissions-matrix.js +++ b/public/scripts/permissions-matrix.js @@ -75,6 +75,14 @@ }); }); }, + clearPermission(index) { + let currentRole = this.permissions[index].role; + this.rawPermissions = this.rawPermissions.filter(p => { + let {type, role} = this.parsePermission(p); + + return role !== currentRole; + }); + }, removePermission(index) { let row = this.permissions.splice(index, 1); if (row.length === 1) { diff --git a/src/Appwrite/Utopia/Response/Filters/V15.php b/src/Appwrite/Utopia/Response/Filters/V15.php index 8e688d58ff..a74f0f32ad 100644 --- a/src/Appwrite/Utopia/Response/Filters/V15.php +++ b/src/Appwrite/Utopia/Response/Filters/V15.php @@ -91,20 +91,17 @@ class V15 extends Filter $parsedResponse[$listKey] = array_map(fn ($content) => $this->parseCreatedAtUpdatedAt($content), $parsedResponse[$listKey]); break; case Response::MODEL_DOCUMENT: + $parsedResponse = $this->parseDocument($parsedResponse); + break; case Response::MODEL_FILE: $parsedResponse = $this->parsePermissionsCreatedAtUpdatedAt($parsedResponse); break; case Response::MODEL_DOCUMENT_LIST: + $listKey = 'documents'; + $parsedResponse[$listKey] = array_map(fn ($content) => $this->parseDocument($content), $parsedResponse[$listKey]); + break; case Response::MODEL_FILE_LIST: - $listKey = ''; - switch ($model) { - case Response::MODEL_DOCUMENT_LIST: - $listKey = 'documents'; - break; - case Response::MODEL_FILE_LIST: - $listKey = 'files'; - break; - } + $listKey = 'files'; $parsedResponse[$listKey] = array_map(fn ($content) => $this->parsePermissionsCreatedAtUpdatedAt($content), $parsedResponse[$listKey]); break; case Response::MODEL_EXECUTION: @@ -210,7 +207,11 @@ class V15 extends Filter protected function parseDatetimeAttributes(array $content, array $attributes): array { foreach ($attributes as $attribute) { - if (isset($content[$attribute])) { + if (array_key_exists($attribute, $content)) { + if (empty($content[$attribute])) { + $content[$attribute] = 0; + continue; + } $content[$attribute] = strtotime($content[$attribute]); } } @@ -314,6 +315,19 @@ class V15 extends Filter return $content; } + protected function parseDocument(array $content) + { + if (isset($content['$collectionId'])) { + $content['$collection'] = $content['$collectionId']; + unset($content['$collectionId']); + } + + unset($content['$databaseId']); + + $content = $this->parsePermissionsCreatedAtUpdatedAt($content); + return $content; + } + private function parseExecution($content) { unset($content['stdout']); diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 5126b8eb8d..0c42c42ff2 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -26,6 +26,15 @@ class UsageTest extends Scope parent::setUp(); } + protected static string $formatTz = 'Y-m-d\TH:i:s.vP'; + + protected function validateDates(array $metrics): void + { + foreach ($metrics as $metric) { + $this->assertIsObject(\DateTime::createFromFormat("Y-m-d\TH:i:s.vP", $metric['date'])); + } + } + public function testPrepareUsersStats(): array { $project = $this->getProject(true); @@ -97,7 +106,9 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['users'])); $this->assertEquals($usersCount, $res['users'][array_key_last($res['users'])]['value']); + $this->validateDates($res['users']); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $res = $this->client->call(Client::METHOD_GET, '/users/usage?range=30d', array_merge($cheaders, [ 'x-appwrite-project' => $projectId, @@ -105,8 +116,11 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals(10, $res['usersCreate'][array_key_last($res['usersCreate'])]['value']); + $this->validateDates($res['usersCreate']); $this->assertEquals(5, $res['usersRead'][array_key_last($res['usersRead'])]['value']); + $this->validateDates($res['usersRead']); $this->assertEquals(5, $res['usersDelete'][array_key_last($res['usersDelete'])]['value']); + $this->validateDates($res['usersDelete']); return ['projectId' => $projectId, 'headers' => $headers, 'requestsCount' => $requestsCount]; } @@ -176,7 +190,7 @@ class UsageTest extends Scope 'path' => realpath(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'), 'name' => 'kitten-1.jpg', ], - ]; + ]; for ($i = 0; $i < 10; $i++) { $file = $files[$i % count($files)]; @@ -257,7 +271,9 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['storage'])); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']); + $this->validateDates($res['storage']); $res = $this->client->call(Client::METHOD_GET, '/storage/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -265,14 +281,23 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']); + $this->validateDates($res['storage']); $this->assertEquals($bucketsCount, $res['bucketsCount'][array_key_last($res['bucketsCount'])]['value']); + $this->validateDates($res['bucketsCount']); $this->assertEquals($bucketsRead, $res['bucketsRead'][array_key_last($res['bucketsRead'])]['value']); + $this->validateDates($res['bucketsRead']); $this->assertEquals($bucketsCreate, $res['bucketsCreate'][array_key_last($res['bucketsCreate'])]['value']); + $this->validateDates($res['bucketsCreate']); $this->assertEquals($bucketsDelete, $res['bucketsDelete'][array_key_last($res['bucketsDelete'])]['value']); + $this->validateDates($res['bucketsDelete']); $this->assertEquals($filesCount, $res['filesCount'][array_key_last($res['filesCount'])]['value']); + $this->validateDates($res['filesCount']); $this->assertEquals($filesRead, $res['filesRead'][array_key_last($res['filesRead'])]['value']); + $this->validateDates($res['filesRead']); $this->assertEquals($filesCreate, $res['filesCreate'][array_key_last($res['filesCreate'])]['value']); + $this->validateDates($res['filesCreate']); $this->assertEquals($filesDelete, $res['filesDelete'][array_key_last($res['filesDelete'])]['value']); + $this->validateDates($res['filesDelete']); $res = $this->client->call(Client::METHOD_GET, '/storage/' . $bucketId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -483,8 +508,11 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['storage'])); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $this->assertEquals($collectionsCount, $res['collections'][array_key_last($res['collections'])]['value']); + $this->validateDates($res['collections']); $this->assertEquals($documentsCount, $res['documents'][array_key_last($res['documents'])]['value']); + $this->validateDates($res['documents']); $res = $this->client->call(Client::METHOD_GET, '/databases/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -492,21 +520,34 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($databasesCount, $res['databasesCount'][array_key_last($res['databasesCount'])]['value']); + $this->validateDates($res['databasesCount']); $this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']); + $this->validateDates($res['collectionsCount']); $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($databasesCreate, $res['databasesCreate'][array_key_last($res['databasesCreate'])]['value']); + $this->validateDates($res['databasesCreate']); $this->assertEquals($databasesRead, $res['databasesRead'][array_key_last($res['databasesRead'])]['value']); + $this->validateDates($res['databasesRead']); $this->assertEquals($databasesDelete, $res['databasesDelete'][array_key_last($res['databasesDelete'])]['value']); + $this->validateDates($res['databasesDelete']); $this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']); + $this->validateDates($res['collectionsCreate']); $this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']); + $this->validateDates($res['collectionsRead']); $this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']); + $this->validateDates($res['collectionsUpdate']); $this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']); + $this->validateDates($res['collectionsDelete']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -514,16 +555,25 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']); + $this->validateDates($res['collectionsCount']); $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']); + $this->validateDates($res['collectionsCreate']); $this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']); + $this->validateDates($res['collectionsRead']); $this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']); + $this->validateDates($res['collectionsUpdate']); $this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']); + $this->validateDates($res['collectionsDelete']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -531,10 +581,14 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $data['requestsCount'] = $requestsCount; return $data; @@ -667,8 +721,11 @@ class UsageTest extends Scope $response = $response['body']; $this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']); + $this->validateDates($response['executionsTotal']); $this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']); + $this->validateDates($response['executionsTime']); $this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']); + $this->validateDates($response['executionsFailure']); $response = $this->client->call(Client::METHOD_GET, '/functions/usage', $headers, [ 'range' => '30d' @@ -688,9 +745,13 @@ class UsageTest extends Scope $response = $response['body']; $this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']); + $this->validateDates($response['executionsTotal']); $this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']); + $this->validateDates($response['executionsTime']); $this->assertGreaterThan(0, $response['buildsTime'][array_key_last($response['buildsTime'])]['value']); + $this->validateDates($response['buildsTime']); $this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']); + $this->validateDates($response['executionsFailure']); } protected function tearDown(): void diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 845510a6b7..627f12d3d1 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -180,89 +180,51 @@ trait UsersBase */ public function testCreateUserSessionHashed(array $data): void { - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'md5@appwrite.io', - 'password' => 'appwrite', - ]); + $userIds = [ 'md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified' ]; - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'md5'); + foreach ($userIds as $userId) { + // Ensure sessions can be created with hashed passwords + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $userId . '@appwrite.io', + 'password' => 'appwrite', + ]); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'bcrypt@appwrite.io', - 'password' => 'appwrite', - ]); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['userId']); + } - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'bcrypt'); + foreach ($userIds as $userId) { + // Ensure all passwords were re-hashed + $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'argon2@appwrite.io', - 'password' => 'appwrite', - ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['$id']); + $this->assertEquals($userId . '@appwrite.io', $response['body']['email']); + $this->assertEquals('argon2', $response['body']['hash']); + $this->assertStringStartsWith('$argon2', $response['body']['password']); + } - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'argon2'); + foreach ($userIds as $userId) { + // Ensure sessions can be created after re-hashing of passwords + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $userId . '@appwrite.io', + 'password' => 'appwrite', + ]); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'sha512@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'sha512'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'scrypt@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'scrypt'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'phpass@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'phpass'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'scrypt-modified@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'scrypt-modified'); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['userId']); + } } /** diff --git a/tests/unit/Utopia/Response/Filters/V15Test.php b/tests/unit/Utopia/Response/Filters/V15Test.php index a68da474d9..ce7870483c 100644 --- a/tests/unit/Utopia/Response/Filters/V15Test.php +++ b/tests/unit/Utopia/Response/Filters/V15Test.php @@ -502,9 +502,34 @@ class V15Test extends TestCase $this->assertEquals($expected, $result); } + public function documentProvider(): array + { + return [ + 'basic document' => [ + [ + '$id' => '5e5ea5c16897e', + '$collectionId' => '5e5ea5c15117e', + '$databaseId' => '5e5ea5c15117e', + '$createdAt' => '2020-06-24T06:47:30.000Z', + '$updatedAt' => '2020-06-24T06:47:30.000Z', + '$permissions' => [Permission::read(Role::any())] + ], + [ + '$id' => '5e5ea5c16897e', + '$collection' => '5e5ea5c15117e', + '$createdAt' => 1592981250, + '$updatedAt' => 1592981250, + '$read' => ['role:all'], + '$write' => [], + ], + ], + ]; + } + /** * @dataProvider createdAtUpdatedAtProvider * @dataProvider permissionsProvider + * @dataProvider documentProvider */ public function testDocument(array $content, array $expected): void { @@ -1077,6 +1102,14 @@ class V15Test extends TestCase 'providerAccessTokenExpiry' => 1592981250, ], ], + 'empty values' => [ + [ + 'providerAccessTokenExpiry' => '', + ], + [ + 'providerAccessTokenExpiry' => 0, + ], + ], ]; } @@ -1089,7 +1122,7 @@ class V15Test extends TestCase $result = $this->filter->parse($content, $model); - $this->assertEquals($expected, $result); + $this->assertSame($expected, $result); } /**