Merge branch 'master' into feat-password-hash-algos

This commit is contained in:
Matej Bačo
2022-07-25 12:37:07 +00:00
908 changed files with 12166 additions and 5800 deletions
+122 -13
View File
@@ -17,11 +17,69 @@ $auth = Config::getParam('auth', []);
*/
$collections = [
'collections' => [
'databases' => [
'$collection' => Database::METADATA,
'$id' => 'databases',
'name' => 'Databases',
'attributes' => [
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'search',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_fulltext_search',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
],
],
'collections' => [
'$collection' => 'databases',
'$id' => 'collections',
'name' => 'Collections',
'attributes' => [
[
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
@@ -98,6 +156,28 @@ $collections = [
'$id' => 'attributes',
'name' => 'Attributes',
'attributes' => [
[
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionInternalId',
'type' => Database::VAR_STRING,
@@ -115,7 +195,7 @@ $collections = [
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
@@ -239,11 +319,11 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_collection',
'$id' => '_key_db_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'attributes' => ['databaseInternalId', 'collectionInternalId'],
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
],
],
@@ -253,6 +333,28 @@ $collections = [
'$id' => 'indexes',
'name' => 'Indexes',
'attributes' => [
[
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionInternalId',
'type' => Database::VAR_STRING,
@@ -270,7 +372,7 @@ $collections = [
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
@@ -344,12 +446,12 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_collection',
'$id' => '_key_db_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
]
'attributes' => ['databaseInternalId', 'collectionInternalId'],
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
],
],
@@ -1200,6 +1302,13 @@ $collections = [
'lengths' => [320],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_phone',
'type' => Database::INDEX_UNIQUE,
'attributes' => ['phone'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_search',
'type' => Database::INDEX_FULLTEXT,
@@ -1767,7 +1876,7 @@ $collections = [
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => '_key_internal',
'$id' => '_key_user',
'type' => Database::INDEX_KEY,
'attributes' => ['userInternalId'],
'lengths' => [Database::LENGTH_KEY],
+15
View File
@@ -48,6 +48,11 @@ return [
'description' => 'SMTP is disabled on your Appwrite instance. You can <a href="/docs/email-delivery">learn more about setting up SMTP</a> in our docs.',
'code' => 503,
],
Exception::GENERAL_PHONE_DISABLED => [
'name' => Exception::GENERAL_PHONE_DISABLED,
'description' => 'Phone provider is not configured. Please check the _APP_PHONE_PROVIDER environment variable of your Appwrite server.',
'code' => 503,
],
Exception::GENERAL_ARGUMENT_INVALID => [
'name' => Exception::GENERAL_ARGUMENT_INVALID,
'description' => 'The request contains one or more invalid arguments. Please refer to the endpoint documentation.',
@@ -170,6 +175,16 @@ return [
'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.',
'code' => 501,
],
Exception::USER_PHONE_ALREADY_EXISTS => [
'name' => Exception::USER_PHONE_ALREADY_EXISTS,
'description' => 'A user with the same phone number already exists in the current project.',
'code' => 409,
],
Exception::USER_PHONE_NOT_FOUND => [
'name' => Exception::USER_PHONE_NOT_FOUND,
'description' => 'The current user does not have a phone number associated with their account.',
'code' => 400,
],
/** Teams */
Exception::TEAM_NOT_FOUND => [
+48 -34
View File
@@ -69,54 +69,68 @@ return [
],
]
],
'collections' => [
'$model' => Response::MODEL_COLLECTION,
'databases' => [
'$model' => Response::MODEL_DATABASE,
'$resource' => true,
'$description' => 'This event triggers on any collection event.',
'documents' => [
'$model' => Response::MODEL_DOCUMENT,
'$description' => 'This event triggers on any database event.',
'collections' => [
'$model' => Response::MODEL_COLLECTION,
'$resource' => true,
'$description' => 'This event triggers on any documents event.',
'$description' => 'This event triggers on any collection event.',
'documents' => [
'$model' => Response::MODEL_DOCUMENT,
'$resource' => true,
'$description' => 'This event triggers on any documents event.',
'create' => [
'$description' => 'This event triggers when a document is created.',
],
'delete' => [
'$description' => 'This event triggers when a document is deleted.'
],
'update' => [
'$description' => 'This event triggers when a document is updated.'
],
],
'indexes' => [
'$model' => Response::MODEL_INDEX,
'$resource' => true,
'$description' => 'This event triggers on any indexes event.',
'create' => [
'$description' => 'This event triggers when an index is created.',
],
'delete' => [
'$description' => 'This event triggers when an index is deleted.'
]
],
'attributes' => [
'$model' => Response::MODEL_ATTRIBUTE,
'$resource' => true,
'$description' => 'This event triggers on any attributes event.',
'create' => [
'$description' => 'This event triggers when an attribute is created.',
],
'delete' => [
'$description' => 'This event triggers when an attribute is deleted.'
]
],
'create' => [
'$description' => 'This event triggers when a document is created.',
'$description' => 'This event triggers when a collection is created.'
],
'delete' => [
'$description' => 'This event triggers when a document is deleted.'
'$description' => 'This event triggers when a collection is deleted.',
],
'update' => [
'$description' => 'This event triggers when a document is updated.'
],
],
'indexes' => [
'$model' => Response::MODEL_INDEX,
'$resource' => true,
'$description' => 'This event triggers on any indexes event.',
'create' => [
'$description' => 'This event triggers when an index is created.',
],
'delete' => [
'$description' => 'This event triggers when an index is deleted.'
]
],
'attributes' => [
'$model' => Response::MODEL_ATTRIBUTE,
'$resource' => true,
'$description' => 'This event triggers on any attributes event.',
'create' => [
'$description' => 'This event triggers when an attribute is created.',
],
'delete' => [
'$description' => 'This event triggers when an attribute is deleted.'
'$description' => 'This event triggers when a collection is updated.',
]
],
'create' => [
'$description' => 'This event triggers when a collection is created.'
'$description' => 'This event triggers when a database is created.'
],
'delete' => [
'$description' => 'This event triggers when a collection is deleted.',
'$description' => 'This event triggers when a database is deleted.',
],
'update' => [
'$description' => 'This event triggers when a collection is updated.',
'$description' => 'This event triggers when a database is updated.',
]
],
'buckets' => [
+14 -14
View File
@@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '8.0.1',
'version' => '9.0.1',
'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' => '5.0.0',
'version' => '6.0.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' => '0.5.0',
'version' => '0.6.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' => '0.6.1',
'version' => '0.7.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' => '5.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -180,7 +180,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '0.17.1',
'version' => '0.18.3',
'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' => '6.0.0',
'version' => '7.0.2',
'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' => '4.0.0',
'version' => '5.0.1',
'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' => '5.0.0',
'version' => '6.0.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' => '0.9.0',
'version' => '0.10.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' => '5.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -352,7 +352,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '5.0.1',
'version' => '6.0.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' => '0.5.0',
'version' => '0.6.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' => '0.5.0',
'version' => '0.6.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+11 -1
View File
@@ -31,6 +31,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'autodesk' => [
'name' => 'Autodesk',
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
'icon' => 'icon-autodesk',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'bitbucket' => [
'name' => 'BitBucket',
'developers' => 'https://developer.atlassian.com/bitbucket',
@@ -117,7 +127,7 @@ return [ // Ordered by ABC.
'icon' => 'icon-gitlab',
'enabled' => true,
'sandbox' => false,
'form' => false,
'form' => 'gitlab.phtml',
'beta' => false,
'mock' => false,
],
+2
View File
@@ -32,6 +32,8 @@ $admins = [
'buckets.write',
'users.read',
'users.write',
'databases.read',
'databases.write',
'collections.read',
'collections.write',
'platforms.read',
+6
View File
@@ -13,6 +13,12 @@ return [ // List of publicly visible scopes
'teams.write' => [
'description' => 'Access to create, update, and delete your project\'s teams',
],
'databases.read' => [
'description' => 'Access to read your project\'s databases',
],
'databases.write' => [
'description' => 'Access to create, update, and delete your project\'s databases',
],
'collections.read' => [
'description' => 'Access to read your project\'s database collections',
],
+11 -8
View File
@@ -53,18 +53,21 @@ return [
'optional' => true,
'icon' => '/images/services/avatars.png',
],
'database' => [
'key' => 'database',
'name' => 'Database',
'subtitle' => 'The Database service allows you to create structured collections of documents, query and filter lists of documents',
'description' => '/docs/services/database.md',
'controller' => 'api/database.php',
'databases' => [
'key' => 'databases',
'name' => 'Databases',
'subtitle' => 'The Databases service allows you to create structured collections of documents, query and filter lists of documents',
'description' => '/docs/services/databases.md',
'controller' => 'api/databases.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/database',
'docsUrl' => 'https://appwrite.io/docs/client/databases',
'tests' => false,
'optional' => true,
'icon' => '/images/services/database.png',
'icon' => '/images/services/databases.png',
'globalAttributes' => [
'databaseId'
]
],
'locale' => [
'key' => 'locale',
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
+6 -6
View File
@@ -68,7 +68,7 @@ return [
'default' => 'localhost',
'required' => true,
'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.' . PHP_EOL . 'You can use the same value as used for the Appwrite hostname.',
'filter' => ''
'filter' => 'domainTarget'
],
[
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
@@ -98,7 +98,7 @@ return [
// ],
[
'name' => '_APP_CONSOLE_WHITELIST_IPS',
'description' => 'This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.',
'description' => "This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.",
'introduction' => '',
'default' => '',
'required' => false,
@@ -340,7 +340,7 @@ return [
],
[
'category' => 'SMTP',
'description' => 'Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user\'s SPAM folder.',
'description' => "Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user\'s SPAM folder.",
'variables' => [
[
'name' => '_APP_SMTP_HOST',
@@ -395,7 +395,7 @@ return [
'variables' => [
[
'name' => '_APP_PHONE_PROVIDER',
'description' => 'Provider used for delivering SMS for Phone authentication. Use the following format: \'phone://[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: 'phone://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.",
'introduction' => '0.15.0',
'default' => '',
'required' => false,
@@ -647,7 +647,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_TIMEOUT',
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds.',
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the function\'s settings or in appwrite.json.',
'introduction' => '0.7.0',
'default' => '900',
'required' => false,
@@ -701,7 +701,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_RUNTIMES',
'description' => "This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\n\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\n\nCurrently, supported environments are: " . \implode(', ', \array_keys(Config::getParam('runtimes'))),
'description' => "This option allows you to enable or disable runtime environments for cloud functions. Disable unused runtimes to save disk space.\n\nTo enable cloud function runtimes, pass a list of enabled environments separated by a comma.\n\nCurrently, supported environments are: " . \implode(', ', \array_keys(Config::getParam('runtimes'))),
'introduction' => '0.8.0',
'default' => 'node-16.0,php-8.0,python-3.9,ruby-3.0',
'required' => false,
+10 -7
View File
@@ -134,6 +134,7 @@ App::post('/v1/account')
});
App::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create Account Session with Email')
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -268,7 +269,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), fn($node) => (!$node['mock'])))) . '.')
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('scopes', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 128 characters long.', true)
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
->inject('project')
@@ -873,7 +874,7 @@ App::post('/v1/account/sessions/phone')
->inject('phone')
->action(function (string $userId, string $number, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, Phone $phone) {
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
throw new Exception('Phone Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
}
$roles = Authorization::getRoles();
@@ -1608,7 +1609,7 @@ App::patch('/v1/account/phone')
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception('Phone number already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception('Phone number already exists', 409, Exception::USER_PHONE_ALREADY_EXISTS);
}
$audits
@@ -2276,12 +2277,12 @@ App::post('/v1/account/verification/phone')
->inject('messaging')
->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) {
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
}
if (empty($user->getAttribute('phone'))) {
throw new Exception('User has no phone number.', 400);
throw new Exception('User has no phone number.', 400, Exception::USER_PHONE_NOT_FOUND);
}
$roles = Authorization::getRoles();
@@ -2314,7 +2315,9 @@ App::post('/v1/account/verification/phone')
$messaging
->setRecipient($user->getAttribute('phone'))
->setMessage($secret);
->setMessage($secret)
->trigger()
;
$events
->setParam('userId', $user->getId())
File diff suppressed because it is too large Load Diff
+17 -9
View File
@@ -33,6 +33,7 @@ use Utopia\Config\Config;
use Cron\CronExpression;
use Executor\Executor;
use Utopia\CLI\Console;
use Utopia\Database\Validator\Permissions;
use Utopia\Validator\Boolean;
include_once __DIR__ . '/../shared/api.php';
@@ -51,7 +52,7 @@ App::post('/v1/functions')
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new CustomId(), 'Function 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('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
->param('execute', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
@@ -100,7 +101,7 @@ App::get('/v1/functions')
->param('limit', 25, new Range(0, 100), 'Maximum number of functions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
@@ -289,7 +290,7 @@ App::put('/v1/functions/:functionId')
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
->param('execute', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
@@ -467,7 +468,7 @@ App::post('/v1/functions/:functionId/deployments')
->inject('project')
->inject('deviceFunctions')
->inject('deviceLocal')
->action(function (string $functionId, string $entrypoint, array $file, bool $activate, Request $request, Response $response, Database $dbForProject, Stats $usage, Event $events, Document $project, Device $deviceFunctions, Device $deviceLocal) {
->action(function (string $functionId, string $entrypoint, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Stats $usage, Event $events, Document $project, Device $deviceFunctions, Device $deviceLocal) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -641,7 +642,7 @@ App::get('/v1/functions/:functionId/deployments')
->param('limit', 25, new Range(0, 100), 'Maximum number of deployments to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the deployment used as the starting point for the query, excluding the deployment itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
@@ -696,7 +697,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
->label('sdk.description', '/docs/references/functions/get-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
@@ -805,7 +806,8 @@ App::post('/v1/functions/:functionId/executions')
->inject('dbForProject')
->inject('user')
->inject('events')
->action(function (string $functionId, string $data, bool $async, Response $response, Document $project, Database $dbForProject, Document $user, Event $events) {
->inject('usage')
->action(function (string $functionId, string $data, bool $async, Response $response, Document $project, Database $dbForProject, Document $user, Event $events, Stats $usage) {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
@@ -889,7 +891,7 @@ App::post('/v1/functions/:functionId/executions')
$events
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->setContext($function);
->setContext('function', $function);
if ($async) {
$event = new Func();
@@ -957,6 +959,12 @@ App::post('/v1/functions/:functionId/executions')
Authorization::skip(fn () => $dbForProject->updateDocument('executions', $executionId, $execution));
$usage
->setParam('functionId', $function->getId())
->setParam('functionExecution', 1)
->setParam('functionStatus', $execution->getAttribute('status', ''))
->setParam('functionExecutionTime', $execution->getAttribute('time') * 1000); // ms
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
@@ -978,7 +986,7 @@ App::get('/v1/functions/:functionId/executions')
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('cursor', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, int $limit, int $offset, string $search, string $cursor, string $cursorDirection, Response $response, Database $dbForProject) {
+6 -6
View File
@@ -113,7 +113,7 @@ App::post('/v1/projects')
$collections = Config::getParam('collections', []);
$dbForProject->setNamespace("_{$project->getInternalId()}");
$dbForProject->create('appwrite');
$dbForProject->create(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$audit = new Audit($dbForProject);
$audit->setup();
@@ -173,7 +173,7 @@ App::get('/v1/projects')
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForConsole')
@@ -278,8 +278,8 @@ App::get('/v1/projects/:projectId/usage')
'network',
'executions',
'users.count',
'database.documents.count',
'database.collections.count',
'databases.documents.count',
'databases.collections.count',
'storage.total'
];
@@ -326,8 +326,8 @@ App::get('/v1/projects/:projectId/usage')
'requests' => $stats['requests'],
'network' => $stats['network'],
'functions' => $stats['executions'],
'documents' => $stats['database.documents.count'],
'collections' => $stats['database.collections.count'],
'documents' => $stats['databases.documents.count'],
'collections' => $stats['databases.collections.count'],
'users' => $stats['users.count'],
'storage' => $stats['storage.total']
]);
+14 -14
View File
@@ -56,8 +56,8 @@ App::post('/v1/storage/buckets')
->param('bucketId', '', new CustomId(), 'Unique 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('name', '', new Text(128), 'Bucket name')
->param('permission', null, new WhiteList(['file', 'bucket']), 'Permissions type model to use for reading files in this bucket. You can use bucket-level permission set once on the bucket using the `read` and `write` params, or you can set file-level permission where each file read and write params will decide who has access to read and write to each file individually. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
@@ -156,7 +156,7 @@ App::get('/v1/storage/buckets')
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('cursor', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket itself. Should be used for efficient pagination when working with large sets of data.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
@@ -224,8 +224,8 @@ App::put('/v1/storage/buckets/:bucketId')
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->param('name', null, new Text(128), 'Bucket name', false)
->param('permission', null, new WhiteList(['file', 'bucket']), 'Permissions type model to use for reading files in this bucket. You can use bucket-level permission set once on the bucket using the `read` and `write` params, or you can set file-level permission where each file read and write params will decide who has access to read and write to each file individually. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
@@ -342,8 +342,8 @@ 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('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -354,7 +354,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(function (string $bucketId, string $fileId, array $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
->action(function (string $bucketId, string $fileId, mixed $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if (
@@ -650,7 +650,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$events
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setContext($bucket)
->setContext('bucket', $bucket)
;
$metadata = null; // was causing leaks as it was passed by reference
@@ -676,7 +676,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
->param('limit', 25, new Range(0, 100), 'Maximum number of files to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
@@ -1288,8 +1288,8 @@ 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('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('response')
->inject('dbForProject')
->inject('user')
@@ -1357,7 +1357,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$events
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setContext($bucket)
->setContext('bucket', $bucket)
;
$audits->setResource('file/' . $file->getId());
@@ -1459,7 +1459,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$events
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setContext($bucket)
->setContext('bucket', $bucket)
->setPayload($response->output($file, Response::MODEL_FILE))
;
+2 -2
View File
@@ -119,7 +119,7 @@ App::get('/v1/teams')
->param('limit', 25, new Range(0, 100), 'Maximum number of teams to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the team used as the starting point for the query, excluding the team itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
@@ -451,7 +451,7 @@ App::get('/v1/teams/:teamId/memberships')
->param('limit', 25, new Range(0, 100), 'Maximum number of memberships to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
+1 -1
View File
@@ -326,7 +326,7 @@ App::get('/v1/users')
->param('limit', 25, new Range(0, 100), 'Maximum number of users to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
+4
View File
@@ -19,6 +19,7 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
use Appwrite\Utopia\Response\Filters\V13 as ResponseV13;
use Appwrite\Utopia\Response\Filters\V14 as ResponseV14;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -177,6 +178,9 @@ App::init(function (App $utopia, Request $request, Response $response, Document
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
+1 -1
View File
@@ -237,7 +237,7 @@ App::post('/v1/mock/tests/general/upload')
->param('file', [], new File(), 'Sample file param', false)
->inject('request')
->inject('response')
->action(function (string $x, int $y, array $z, array $file, Request $request, Response $response) {
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
$file = $request->getFiles('file');
+7 -5
View File
@@ -2,6 +2,7 @@
use Appwrite\Auth\Auth;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
@@ -18,7 +19,7 @@ use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, Event $database, Database $dbForProject, string $mode) {
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
$route = $utopia->match($request);
@@ -164,7 +165,7 @@ App::init(function (App $utopia, Request $request, Document $project) {
}
}, ['utopia', 'request', 'project'], 'auth');
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, Event $database, string $mode, Database $dbForProject) {
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
if (!empty($events->getEvent())) {
if (empty($events->getPayload())) {
@@ -192,16 +193,17 @@ App::shutdown(function (App $utopia, Request $request, Response $response, Docum
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
$payload = new Document($events->getPayload());
$context = $events->getContext() ?? false;
$collection = ($context && $context->getCollection() === 'collections') ? $context : null;
$bucket = ($context && $context->getCollection() === 'buckets') ? $context : null;
$db = $events->getContext('database');
$collection = $events->getContext('collection');
$bucket = $events->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
+53 -12
View File
@@ -215,21 +215,56 @@ App::get('/console/keys')
->setParam('body', $page);
});
App::get('/console/database')
App::get('/console/databases')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/database/index.phtml');
$page = new View(__DIR__ . '/../../views/console/databases/index.phtml');
$layout
->setParam('title', APP_NAME . ' - Database')
->setParam('body', $page);
});
App::get('/console/database/collection')
App::get('/console/databases/database')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Database unique ID.')
->inject('response')
->inject('layout')
->action(function (string $id, Response $response, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listLogs')
->setParam('params', [
'database-id' => '{{router.params.id}}',
])
;
$page = new View(__DIR__ . '/../../views/console/databases/database.phtml');
$page->setParam('logs', $logs);
$layout
->setParam('title', APP_NAME . ' - Database')
->setParam('body', $page)
;
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Expires', 0)
->addHeader('Pragma', 'no-cache')
;
});
App::get('/console/databases/collection')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
@@ -242,13 +277,14 @@ App::get('/console/database/collection')
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listCollectionLogs')
->setParam('method', 'databases.listCollectionLogs')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
'database-id' => '{{router.params.databaseId}}'
])
;
$page = new View(__DIR__ . '/../../views/console/database/collection.phtml');
$page = new View(__DIR__ . '/../../views/console/databases/collection.phtml');
$page->setParam('logs', $logs);
@@ -264,29 +300,32 @@ App::get('/console/database/collection')
;
});
App::get('/console/database/document')
App::get('/console/databases/document')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('databaseId', '', new UID(), 'Database unique ID.')
->param('collection', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function (string $collection, View $layout) {
->action(function (string $databaseId, string $collection, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listDocumentLogs')
->setParam('method', 'databases.listDocumentLogs')
->setParam('params', [
'database-id' => '{{router.params.databaseId}}',
'collection-id' => '{{router.params.collection}}',
'document-id' => '{{router.params.id}}',
])
;
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
$page
->setParam('new', false)
->setParam('database', $databaseId)
->setParam('collection', $collection)
->setParam('logs', $logs)
;
@@ -296,18 +335,20 @@ App::get('/console/database/document')
->setParam('body', $page);
});
App::get('/console/database/document/new')
App::get('/console/databases/document/new')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('databaseId', '', new UID(), 'Database unique ID.')
->param('collection', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function (string $collection, View $layout) {
->action(function (string $databaseId, string $collection, View $layout) {
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
$page
->setParam('new', true)
->setParam('database', $databaseId)
->setParam('collection', $collection)
->setParam('logs', new View())
;
Binary file not shown.
Binary file not shown.
+7
View File
@@ -118,6 +118,13 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
if (!$dbForConsole->getCollection($key)->isEmpty()) {
continue;
}
/**
* Skip to prevent 0.15 migration issues.
*/
if ($key === 'databases' && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'collections')) {
continue;
}
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
$attributes = [];
+15 -7
View File
@@ -27,6 +27,8 @@ use Appwrite\Auth\Phone\Mock;
use Appwrite\Auth\Phone\Telesign;
use Appwrite\Auth\Phone\TextMagic;
use Appwrite\Auth\Phone\Twilio;
use Appwrite\Auth\Phone\Msg91;
use Appwrite\Auth\Phone\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
@@ -84,9 +86,10 @@ const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
const APP_LIMIT_COMPRESSION = 20000000; //20MB
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_SUBQUERY = 1000;
const APP_CACHE_BUSTER = 400;
const APP_VERSION_STABLE = '0.15.0';
const APP_CACHE_BUSTER = 402;
const APP_VERSION_STABLE = '0.15.2';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -124,6 +127,7 @@ const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_PROJECTS = 'projects';
@@ -263,8 +267,9 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('attributes', [
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], $database->getAttributeLimit());
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
], $database->getAttributeLimit(), 0, []);
}
);
@@ -276,7 +281,8 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('indexes', [
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
], 64);
}
);
@@ -433,7 +439,7 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
* Registry
*/
$register->set('logger', function () {
// Register error logger
// Register error logger
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
@@ -553,7 +559,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-03.mmdb');
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-06.mmdb');
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
@@ -986,6 +992,8 @@ App::setResource('phone', function () {
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),
'msg91' => new Msg91($user, $secret),
'vonage' => new Vonage($user, $secret),
default => null
};
});
+31 -21
View File
@@ -112,8 +112,9 @@ function getDatabase(Registry &$register, string $namespace)
if (!$database->exists($database->getDefaultDatabase(), 'realtime')) {
throw new Exception('Collection not ready');
}
break; // leave loop if successful
} catch (\Exception $e) {
} catch (\Throwable $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
@@ -138,25 +139,30 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
/**
* Create document for this worker to share stats across Containers.
*/
go(function () use ($register, $containerId, &$statsDocument, $logError) {
try {
[$database, $returnDatabase] = getDatabase($register, '_console');
$document = new Document([
'$id' => $database->getId(),
'$collection' => 'realtime',
'$read' => [],
'$write' => [],
'container' => $containerId,
'timestamp' => time(),
'value' => '{}'
]);
go(function () use ($register, $containerId, &$statsDocument) {
$attempts = 0;
[$database, $returnDatabase] = getDatabase($register, '_console');
do {
try {
$attempts++;
$document = new Document([
'$id' => $database->getId(),
'$collection' => 'realtime',
'$read' => [],
'$write' => [],
'container' => $containerId,
'timestamp' => time(),
'value' => '{}'
]);
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
} catch (\Throwable $th) {
call_user_func($logError, $th, "createWorkerDocument");
} finally {
call_user_func($returnDatabase);
}
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (\Throwable $th) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
sleep(DATABASE_RECONNECT_SLEEP);
}
} while (true);
call_user_func($returnDatabase);
});
/**
@@ -485,8 +491,12 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace("_console");
$project = Authorization::skip(fn() => $database->getDocument('projects', $realtime->connections[$connection]['projectId']));
$database->setNamespace("_{$project->getInternalId()}");
$projectId = $realtime->connections[$connection]['projectId'];
if ($projectId !== 'console') {
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
$database->setNamespace("_{$project->getInternalId()}");
}
/*
* Abuse Check
+10
View File
@@ -173,6 +173,16 @@ $cli
if (empty($input[$var['name']])) {
$input[$var['name']] = $var['default'];
}
if ($var['filter'] === 'domainTarget') {
if ($input[$var['name']] !== 'localhost') {
Console::warning("\nIf you haven't already done so, set the following record for {$input[$var['name']]} on your DNS provider:\n");
$mask = "%-15.15s %-10.10s %-30.30s\n";
printf($mask, "Type", "Name", "Value");
printf($mask, "A or AAAA", "@", "<YOUR PUBLIC IP>");
Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n");
}
}
}
$templateForCompose = new View(__DIR__ . '/../views/install/compose.phtml');
+4 -3
View File
@@ -28,9 +28,9 @@ $cli
Console::success('Starting Data Migration to version ' . $version);
$db = $register->get('db', true);
$cache = $register->get('cache', true);
$cache = new Cache(new RedisCache($cache));
$redis = $register->get('cache', true);
$redis->flushAll();
$cache = new Cache(new RedisCache($redis));
$projectDB = new Database(new MariaDB($db), $cache);
$projectDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
@@ -79,5 +79,6 @@ $cli
}
Swoole\Event::wait(); // Wait for Coroutines to finish
$redis->flushAll();
Console::success('Data Migration Completed');
});
+74 -84
View File
@@ -123,104 +123,94 @@ $cli
],
];
foreach (['swagger2', 'open-api3'] as $format) {
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlaforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlaforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlaforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlaforms[] = APP_PLATFORM_CONSOLE;
break;
}
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
\is_array($modelLabel) ? \array_map(function ($m) use ($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']
) {
if (empty($routeSecurity)) {
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlaforms)) {
continue;
}
$routes[] = $route;
}
}
foreach (Config::getParam('services', []) as $service) {
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']
) {
continue;
}
switch ($format) {
case 'swagger2':
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
'x-globalAttributes' => $service['globalAttributes'] ?? [],
];
}
case 'open-api3':
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
$models = $response->getModels();
default:
throw new Exception('Format not found: ' . $format);
break;
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
// var_dump($models);
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
foreach (['swagger2', 'open-api3'] as $format) {
$formatInstance = match ($format) {
'swagger2' => new Swagger2(...$arguments),
'open-api3' => new OpenAPI3(...$arguments),
default => throw new Exception('Format not found: ' . $format)
};
$specs = new Specification($formatInstance);
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
+1 -1
View File
@@ -83,7 +83,7 @@ $logError = function (Throwable $error, string $action = 'syncUsageStats') use (
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("realtime");
$log->setNamespace("usage");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
+1 -1
View File
@@ -97,7 +97,7 @@
<ul class="links">
<li>
<a data-ls-attrs="href=/console/database?project={{router.params.project}}"
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
@@ -1,57 +0,0 @@
<?php
$key = $this->getParam('key', '');
$type = $this->getParam('type', '');
$required = $this->getParam('required', '');
$comp = $this->getParam('comp', '');
$namespace = $this->getParam('namespace', '');
$list = $this->getParam('list', []);
$collections = $this->getParam('collections', []);
$comp->setParam('namespace', 'node');
if($type === 'document') {
$comp->setParam('key', null);
}
?>
<input type="hidden" name="<?php echo $this->escape($key); ?>"<?php if($required): ?> required<?php endif; ?> data-cast-to="array-empty">
<hr />
<ul data-ls-loop="<?php echo $this->escape($namespace); ?>" data-ls-as="node" class="sortable numbers">
<li data-forms-move-up data-forms-move-down>
<div class="drop-list bottom end settings" data-ls-ui-open="" data-button-text="" data-button-icon="icon-cog" data-button-aria="Options" data-button-selector="[data-toggler]" data-button-class="round dark small margin-bottom-small margin-top-tiny pull-end" data-blur="1">
<ul class="arrow-end margin-top margin-end-negative-small">
<li data-move-up>
<button type="button" class="link"><i class="icon-up-dir"></i> Move Up</button>
</li>
<li data-move-down>
<button type="button" class="link"><i class="icon-down-dir"></i> Move Down</button>
</li>
<li>
<button type="button" data-ls-ui-trigger="splice-<?php echo $this->escape($namespace); ?>-{{$index}}" class="link"><i class="icon-cancel"></i> Remove</button>
</li>
</ul>
</div>
<?php echo $comp->render(); ?>
<hr />
</li>
</ul>
<?php if(!empty($list) && $type === 'document'): ?>
<div class="drop-list" data-ls-ui-open="" data-button-text="Add" data-button-aria="Add" data-button-icon="" data-button-selector="[data-toggler]" data-button-class="reverse margin-bottom-small" data-blur="1">
<ul>
<?php foreach($list as $item):
$name = (isset($collections[$item])) ? $collections[$item]->getAttribute('name', '') : '';
?>
<li>
<button type="button" class="link" data-ls-ui-trigger="collection-child-<?php echo $this->escape($namespace); ?>-<?php echo $this->escape($item); ?>"><i class="icon-edit"></i> Add <?php echo $this->escape($name); ?></button>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php else: ?>
<button type="button" data-ls-ui-trigger="collection-child-<?php echo $this->escape($namespace); ?>" class="reverse margin-bottom-small">Add</button>
<?php endif; ?>
@@ -1,7 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="hidden" data-forms-switch data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-cast-to="boolean"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no" />
@@ -1,19 +0,0 @@
<?php
$namespace = $this->getParam('namespace', '');
$array = $this->getParam('array', false);
$list = $this->getParam('list', []);
$list = (is_array($list)) ? $list : [];
$list = ($array) ? $list : array_shift($list);
?>
<?php if($array): ?>
<div data-ls-template="collection-array-{{<?php echo $this->escape($namespace); ?>.$collection}}" data-type="script" class="margin-bottom-negative-small"></div>
<?php else: ?>
<hr class="margin-top-no" />
<div class="clear">
<a data-ls-if="(!{{<?php echo $this->escape($namespace); ?>.$id}})" data-ls-attrs="href=/console/database/document?&collection=<?php echo $this->escape($list); ?>&project={{router.params.project}}" class="pull-end text-size-small">New Document <i class="icon-link-ext"></i></a>
<a data-ls-if="({{<?php echo $this->escape($namespace); ?>.$id}})" data-ls-attrs="href=/console/database/document?id={{<?php echo $this->escape($namespace); ?>.$id}}&collection=<?php echo $this->escape($list); ?>&project={{router.params.project}}" class="pull-end text-size-small"><span data-ls-bind="Document #{{<?php echo $this->escape($namespace); ?>.$id}}"></span> <i class="icon-link-ext"></i></a>
</div>
<div data-ls-template="collection-<?php echo $this->escape($list); ?>" data-type="script" class="margin-bottom-negative-small"></div>
<hr />
<?php endif; ?>
@@ -1,57 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
$collections = $this->getParam('collections', []);
$array = $this->getParam('array', false);
$list = $this->getParam('list', []);
$list = (is_array($list)) ? $list : [];
?>
<?php foreach($list as $item):
$collection = $collections[$item] ?? null;
if(empty($collection)) {
continue;
}
$rules = $collection->getAttribute('rules', []);
?>
<div data-ls-if="({{<?php echo $this->escape($namespace); ?>}})"
data-service="database.getDocument"
data-param-collection-id="<?php echo $this->escape($collection->getId()); ?>"
data-param-document-id="{{<?php echo $this->escape($namespace); ?>}}"
data-scope="sdk"
data-event="load,document-selected-<?php echo $this->escape($collection->getId()); ?>"
data-name="project-document-preview"
data-success="default">
<div class="box line margin-bottom-small padding-small fade-bottom">
<ul>
<?php foreach($rules as $i => $rule):
$collectionLabel = $rule['label'] ?? '';
$collectionKey = $rule['key'] ?? '';
if($i === 3) {break;}
?>
<li class="margin-bottom-small">
<p><b><?php echo $this->escape($collectionLabel); ?>: </b> <span data-ls-bind="{{project-document-preview.<?php echo $this->escape($collectionKey); ?>|limit}}" data-unsync="1"></span></p>
</li>
<?php endforeach; ?>
</ul>
<span class="label tag blue">preview</span>
</div>
</div>
<input
type="text" data-forms-document-preview="<?php echo $this->escape($collection->getId()); ?>"
name="<?php echo $this->escape($key); ?>"
data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"
class="margin-bottom" disabled<?php if($required): ?> required<?php endif; ?>>
<button class="reverse margin-end-small" type="button"
data-forms-document="open-document-serach-<?php echo $this->escape($collection->getId()); ?>"
data-search="<?php echo $this->escape($namespace); ?>"><i class="icon-search"></i> <?php echo $this->escape($collection->getAttribute('name')); ?></button>
<?php endforeach; ?>
@@ -1,6 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="email" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" placeholder="me@example.com"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no">
@@ -1,20 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input
type="hidden"
name="<?php echo $this->escape($key); ?>"
data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"
data-read="<?php echo $this->escape(json_encode([])); ?>"
data-write="<?php echo $this->escape(json_encode([])); ?>"
data-accept=""
data-forms-upload=""
data-label-button="Upload"
data-search="<?php echo $this->escape($namespace); ?>"
data-scope="sdk"
data-default=""
data-project="{{router.params.project}}"
<?php if($required): ?> required<?php endif; ?>
class="margin-bottom-no">
@@ -1,6 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" minlength="7" maxlength="15" size="15" pattern="^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$" title="Please enter a valid IPV4 address" placeholder="255.255.255.255"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no">
@@ -1,7 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<textarea type="text" name="<?php echo $this->escape($key); ?>" data-forms-pell data-forms-text-direction data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"<?php if($required): ?> required<?php endif; ?>></textarea>
@@ -1,7 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="number" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-cast-to="numeric"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no" step=any />
@@ -1,6 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-forms-text-direction data-forms-text-count<?php if($required): ?> required<?php endif; ?> />
@@ -1,6 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<input name="<?php echo $this->escape($key); ?>" type="url" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" placeholder="https://example.com"<?php if($required): ?> required<?php endif; ?> />
@@ -1,6 +0,0 @@
<?php
$key = $this->getParam('key', '');
$required = $this->getParam('required', '');
$namespace = $this->getParam('namespace', '');
?>
<textarea name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-forms-text-direction data-forms-text-count<?php if($required): ?> required<?php endif; ?>></textarea>
@@ -1,148 +0,0 @@
<?php
$collection = $this->getParam('collection', []);
$id = $collection->getId();
$name = $collection->getAttribute('name', 'Collection');
$rules = $collection->getAttribute('rules', []);
?>
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-document-serach-<?php echo $this->escape($id); ?>" data-close-event="none">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2><?php echo $this->escape($name); ?> Search</h2>
<form class="search margin-bottom"
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="<?php echo $this->escape($id); ?>"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="0"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-documents"
data-success="state"
data-success-param-state-keys="search,offset">
<input name="search" id="searchDocuments-<?php echo $this->escape($id); ?>" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
</form>
<div
data-service="database.listDocuments"
data-event="open-document-serach-<?php echo $this->escape($id); ?>"
data-param-collection-id="<?php echo $this->escape($id); ?>"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="0"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-documents">
<div data-ls-if="0 == {{project-documents.total}}" class="margin-bottom">
<h3 class="margin-bottom-small text-bold">No Documents Found</h3>
<p class="margin-bottom-no">Try a different search term.</p>
</div>
<div data-ls-if="({{project-documents.total}})">
<form class="scroll">
<table class="margin-top-no margin-bottom-no">
<thead>
<tr>
<th width="40">&nbsp;</th>
<?php foreach($rules as $rule):
$label = $rule['label'] ?? '';
?>
<th width="120"><?php echo $this->escape($label); ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody data-ls-loop="project-documents.documents" data-ls-as="node">
<tr>
<td data-title="x" class="">
<!-- <input type="radio" name="selected" data-ls-attrs="value={{file.$id}}" data-ls-bind="{{search.selected}}" /> -->
<input type="radio" name="selected" data-ls-attrs="value={{node.$id}}" data-ls-bind="{{search.selected}}" />
</td>
<?php foreach($rules as $rule):
$label = $rule['label'] ?? '';
$key = $rule['key'] ?? '';
$type = $rule['type'] ?? '';
$array = $rule['array'] ?? '';
?>
<td data-title="<?php echo $this->escape($label); ?>" class="text-size-small text-height-small">
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection=<?php echo $this->escape($id); ?>&project={{router.params.project}}" target="_blank">
<?php if(!$array): ?>
<?php switch($type):
case 'fileId': ?>
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
<?php break; ?>
<?php case 'document': ?>
{...}
<?php break; ?>
<?php default: ?>
<span data-ls-bind="{{node.<?php echo $this->escape($key); ?>}}" data-ls-attrs="title={{node.<?php echo $this->escape($key); ?>}}"></span>
<?php break; ?>
<?php endswitch; ?>
<?php else: ?>
[...]
<?php endif; ?>
</a>
</td>
<?php endforeach; ?>
</tr>
</tbody>
</table>
</form>
</div>
</div>
<footer>
<div class="clear text-align-center paging pull-end">
<form
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="<?php echo $this->escape($id); ?>"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-documents"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-documents.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-documents.total|pageTotal}}"></span>
<form
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="<?php echo $this->escape($id); ?>"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-documents"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-documents.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<form data-service="container.path"
data-param-path="search.selected"
data-param-type="assign"
data-param-value="{{node.$id}}"
data-success="trigger"
data-success-param-trigger-events="modal-close,document-selected-<?php echo $this->escape($id); ?>"
data-event="click"
data-scope="window.ls">
<input type="hidden" name="path" data-ls-bind="{{search.path}}" />
<input type="hidden" name="type" value="assign" />
<input type="hidden" name="value" data-ls-bind="{{search.selected}}" />
<button data-ls-if="({{search.selected}})" type="button" class="">Select</button> &nbsp;
<button data-ls-if="(!{{search.selected}})" type="button" class="" disabled>Select</button> &nbsp;
</form>
<button data-ui-modal-close="" type="button" class="reverse desktops-only-inline">Cancel</button>
</footer>
</div>
@@ -1,131 +0,0 @@
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-file-serach" data-close-event="none">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>File Search</h2>
<form class="search margin-bottom"
data-service="storage.listFiles"
data-event="submit"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset=""
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<input name="search" id="searchFiles" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
</form>
<div
data-service="storage.listFiles"
data-event="open-file-serach"
data-param-search=""
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="0"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<div data-ls-if="0 == {{project-files.total}}" class="margin-bottom">
<h3 class="margin-bottom-small text-bold">No Files Found</h3>
<p class="margin-bottom-no">Try a different search term.</p>
</div>
<div data-ls-if="0 != {{project-files.total}}">
<div class="scroll">
<table class="margin-top-no margin-bottom-no">
<thead>
<tr>
<th width="40">&nbsp;</th>
<th width="40">&nbsp;</th>
<th width="100">Filename</th>
<th width="100">Type</th>
<th width="100">Size</th>
<th width="80">Created</th>
<!-- <th width="30"></th> -->
</tr>
</thead>
<tbody data-ls-loop="project-files.files" data-ls-as="file">
<tr>
<td data-title="x" class="">
<input type="radio" name="selected" data-ls-attrs="value={{file.$id}}" data-ls-bind="{{search.selected}}" />
</td>
<td data-title="x" class="">
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
</td>
<td data-title="Name: " class="text-one-liner">
<span data-ls-bind="{{file.name}}" data-ls-attrs="title={{file.name}}" class="text-fade text-size-small"></span>
</td>
<td data-title="Type: ">
<span data-ls-bind="{{file.mimeType}}" class="text-fade text-size-small"></span>
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.$createdAt|dateText}}"></span>
</td>
<!-- <td class="hide">
<a target="_blank" data-ls-attrs="href="><i class="icon-link-ext"></i></a>
</td> -->
</tr>
</tbody>
</table>
</div>
</div>
</div>
<footer>
<div class="clear text-align-center paging pull-end">
<form
data-service="storage.listFiles"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" class="margin-end-small round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-files.total|pageTotal}}"></span>
<form
data-service="storage.listFiles"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" class="margin-start-small round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<form data-service="container.path"
data-param-path="search.selected"
data-param-type="assign"
data-param-value="{{file.$id}}"
data-success="trigger"
data-success-param-trigger-events="modal-close"
data-event="click"
data-scope="window.ls">
<input type="hidden" name="path" data-ls-bind="{{search.path}}" />
<input type="hidden" name="type" value="assign" />
<input type="hidden" name="value" data-ls-bind="{{search.selected}}" />
<button data-ls-if="({{search.selected}})" type="button" class="">Select</button> &nbsp;
<button data-ls-if="(!{{search.selected}})" type="button" class="" disabled>Select</button> &nbsp;
</form>
<button data-ui-modal-close="" type="button" class="reverse desktops-only-inline">Cancel</button>
</footer>
</div>
@@ -4,20 +4,28 @@ $logs = $this->getParam('logs', null);
?>
<div
data-service="database.getCollection"
data-service="databases.getCollection"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.id}}"
data-scope="sdk"
data-event="load,database.updateCollection,database.createAttribute,database.deleteAttribute,database.createIndex,database.deleteIndex"
data-event="load,databases.updateCollection,databases.createAttribute,databases.deleteAttribute,databases.createIndex,databases.deleteIndex"
data-name="project-collection">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/database?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Database</a>
<div
data-service="databases.get"
data-param-database-id="{{router.params.databaseId}}"
data-scope="sdk"
data-event="load,databases.update"
data-name="project-database">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/databases/database?id={{router.params.databaseId}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-database.name}}">&nbsp;&nbsp;</span></a>
<br />
<br />
<span data-ls-bind="{{project-collection.name}}">&nbsp;&nbsp;</span>
</h1>
<span data-ls-bind="{{project-collection.name}}">&nbsp;&nbsp;</span>
</h1>
</div>
</div>
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
@@ -34,14 +42,15 @@ $logs = $this->getParam('logs', null);
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/database/collection?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/databases/collection?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2>Documents</h2>
<div
data-service="database.listDocuments"
data-event="load,database.createDocument,database.updateDocument,database.deleteDocument"
data-service="databases.listDocuments"
data-event="load,databases.createDocument,databases.updateDocument,databases.deleteDocument"
data-param-collection-id="{{router.params.id}}"
data-param-database-id="{{router.params.databaseId}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
data-param-order-types-cast-to="array"
@@ -75,15 +84,15 @@ $logs = $this->getParam('logs', null);
</template>
</tr>
</thead>
<tbody data-ls-attrs="x-init=documents = {{project-documents.documents}}" x-data="{documents: []}">
<tbody data-ls-attrs="x-init=documents = {{project-documents.documents}}; databaseId='{{router.params.databaseId}}'" x-data="{documents: []}">
<template x-for="doc in documents">
<tr>
<td data-title="$id: ">
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`" x-text="doc.$id"></a>
<a :href="`/console/databases/document?id=${doc.$id}&collection=${doc.$collection}&databaseId=${databaseId}&project=${project}`" x-text="doc.$id"></a>
</td>
<template x-for="attr in attributes">
<td x-show="attr.status === 'available'" :data-title="attr.key + ':'">
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`">
<a :href="`/console/databases/document?id=${doc.$id}&collection=${doc.$collection}&databaseId=${databaseId}&project=${project}`">
<span x-text="doc[attr.key] ?? 'n/a'"></span>
</a>
</td>
@@ -98,8 +107,9 @@ $logs = $this->getParam('logs', null);
<div class="pull-end text-align-center paging">
<form
data-service="database.listDocuments"
data-service="databases.listDocuments"
data-event="submit"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
@@ -114,8 +124,9 @@ $logs = $this->getParam('logs', null);
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-documents.total|pageTotal}}"></span>
<form
data-service="database.listDocuments"
data-service="databases.listDocuments"
data-event="submit"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
@@ -128,15 +139,15 @@ $logs = $this->getParam('logs', null);
</form>
</div>
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/document/new?collection={{router.params.id}}&project={{router.params.project}}" class="button">
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/databases/document/new?collection={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="button">
Add Document
</a>
<a data-ls-if="!{{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/collection/attributes?id={{router.params.id}}&project={{router.params.project}}" class="button">
<a data-ls-if="!{{project-collection.attributes.length}}" data-ls-attrs="href=/console/databases/collection/attributes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="button">
Create Attribute
</a>
</div>
</li>
<li data-state="/console/database/collection/attributes?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/databases/collection/attributes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2>Attributes</h2>
<div class="clear box margin-bottom">
@@ -266,13 +277,14 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Attribute"
data-service="database.deleteAttribute"
data-service="databases.deleteAttribute"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this attribute?"
data-param-database-id="{{router.params.databaseId}}"
data-success="alert,trigger"
data-success-param-alert-text="Deleted attribute successfully"
data-success-param-trigger-events="database.deleteAttribute"
data-success-param-trigger-events="databases.deleteAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to delete attribute"
data-failure-param-alert-classname="error">
@@ -320,7 +332,7 @@ $logs = $this->getParam('logs', null);
</ul>
</div>
</li>
<li data-state="/console/database/collection/indexes?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/databases/collection/indexes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2>Indexes</h2>
<div class="clear box margin-bottom">
@@ -382,13 +394,14 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Index"
data-service="database.deleteIndex"
data-service="databases.deleteIndex"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this index?"
data-param-database-id="{{router.params.databaseId}}"
data-success="alert,trigger"
data-success-param-alert-text="Deleted index successfully"
data-success-param-trigger-events="database.deleteIndex"
data-success-param-trigger-events="databases.deleteIndex"
data-failure="alert"
data-failure-param-alert-text="Failed to delete index"
data-failure-param-alert-classname="error">
@@ -407,18 +420,19 @@ $logs = $this->getParam('logs', null);
<button class="new-index">Add Index</button>
</li>
<li data-state="/console/database/collection/activity?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/databases/collection/activity?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2>Activity</h2>
<?php echo $logs->render(); ?>
</li>
<li data-state="/console/database/collection/usage?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/databases/collection/usage?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="database.getCollectionUsage"
data-service="databases.getCollectionUsage"
data-event="submit"
data-name="usage"
data-param-collection-id="{{router.params.id}}"
data-param-database-id="{{router.params.databaseId}}"
data-param-range="90d">
<button class="tick">90d</button>
</form>
@@ -427,9 +441,10 @@ $logs = $this->getParam('logs', null);
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="database.getCollectionUsage"
data-service="databases.getCollectionUsage"
data-event="submit"
data-name="usage"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.id}}">
<button class="tick">30d</button>
</form>
@@ -438,9 +453,10 @@ $logs = $this->getParam('logs', null);
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="database.getCollectionUsage"
data-service="databases.getCollectionUsage"
data-event="submit"
data-name="usage"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.id}}"
data-param-range="24h">
<button class="tick">24h</button>
@@ -450,7 +466,7 @@ $logs = $this->getParam('logs', null);
<h2>Usage</h2>
<div data-service="database.getCollectionUsage" data-event="load" data-name="usage" data-param-collection-id="{{router.params.id}}">
<div data-service="databases.getCollectionUsage" data-event="load" data-name="usage" data-param-collection-id="{{router.params.id}}" data-param-database-id="{{router.params.databaseId}}">
<h3 class="margin-bottom-tiny">Documents</h3>
<p class="text-fade">Count of documents over time</p>
<div class="box margin-bottom-small">
@@ -489,7 +505,7 @@ $logs = $this->getParam('logs', null);
</ul>
</div>
</li>
<li data-state="/console/database/collection/settings?id={{router.params.id}}&project={{router.params.project}}">
<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">
@@ -500,13 +516,14 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Database Collection"
data-service="database.updateCollection"
data-service="databases.updateCollection"
data-scope="sdk"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-database-id="{{router.params.databaseId}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated collection successfully"
data-success-param-trigger-events="database.updateCollection"
data-success-param-trigger-events="databases.updateCollection"
data-failure="alert"
data-failure-param-alert-text="Failed to update collection"
data-failure-param-alert-classname="error">
@@ -567,26 +584,36 @@ $logs = $this->getParam('logs', null);
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Collection)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-collection.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-collection.$createdAt|dateText}}"></span></li>
</ul>
<form
name="database.deleteCollection" class="margin-bottom"
name="databases.deleteCollection" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Database Collection"
data-service="database.deleteCollection"
data-service="databases.deleteCollection"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-database-id="{{router.params.databaseId}}"
data-confirm="Are you sure you want to delete this collection?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Collection deleted successfully"
data-success-param-trigger-events="database.deleteCollection"
data-success-param-redirect-url="/console/database?project={{router.params.project}}"
data-success-param-trigger-events="databases.deleteCollection"
data-success-param-redirect-url="/console/databases?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete collection"
data-failure-param-alert-classname="error">
@@ -611,12 +638,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (string)"
data-service="database.createStringAttribute"
data-service="databases.createStringAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -625,6 +652,7 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<label for="string-key">Attribute ID</label>
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" />
@@ -667,12 +695,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (integer)"
data-service="database.createIntegerAttribute"
data-service="databases.createIntegerAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -680,6 +708,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false, min: null, max: null }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="integer-key">Attribute ID</label>
@@ -732,12 +761,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-service="databases.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -745,6 +774,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false, min: null, max: null }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="float-key">Attribute ID</label>
@@ -797,12 +827,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-service="databases.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -810,6 +840,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="email-key">Attribute ID</label>
@@ -850,12 +881,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (boolean)"
data-service="database.createBooleanAttribute"
data-service="databases.createBooleanAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -863,6 +894,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="boolean-key">Attribute ID</label>
@@ -907,12 +939,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (IP)"
data-service="database.createIpAttribute"
data-service="databases.createIpAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -920,6 +952,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="ip-key">Attribute ID</label>
@@ -960,12 +993,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (url)"
data-service="database.createUrlAttribute"
data-service="databases.createUrlAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -973,6 +1006,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="url-key">Attribute ID</label>
@@ -1013,12 +1047,12 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (enum)"
data-service="database.createEnumAttribute"
data-service="databases.createEnumAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-success-param-trigger-events="databases.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@@ -1026,6 +1060,7 @@ $logs = $this->getParam('logs', null);
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="enum-key">Attribute ID</label>
@@ -1093,17 +1128,18 @@ $logs = $this->getParam('logs', null);
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Index"
data-service="database.createIndex"
data-service="databases.createIndex"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new index successfully"
data-success-param-trigger-events="database.createIndex"
data-success-param-trigger-events="databases.createIndex"
data-failure="alert"
data-failure-param-alert-text="Failed to create index"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="index-key">Index Key</label>
@@ -1125,8 +1161,15 @@ $logs = $this->getParam('logs', null);
<div data-forms-clone="" data-label="Add Attribute" data-target="attributes-section" data-first="1">
<div class="row responsive thin margin-bottom-tiny">
<div class="col span-7 margin-bottom-small">
<select data-duplications data-ls-attrs="name=attributes" data-ls-loop="project-collection.attributes" data-ls-as="option" data-cast-to="array" class="margin-bottom-no">
<option data-ls-attrs="value={{option.key}}" data-ls-bind="{{option.key}}"></option>
<select data-duplications data-ls-attrs="name=attributes" data-cast-to="array" class="margin-bottom-no">
<optgroup label="Internal">
<option value="$id">$id</option>
<option value="$createdAt">$createdAt</option>
<option value="$updatedAt">$updatedAt</option>
</optgroup>
<optgroup label="Attributes" data-ls-loop="project-collection.attributes" data-ls-as="option">
<option data-ls-attrs="value={{option.key}}" data-ls-bind="{{option.key}}"></option>
</optgroup>
</select>
</div>
<div class="col span-4 margin-bottom-small">
@@ -1148,7 +1191,7 @@ $logs = $this->getParam('logs', null);
</form>
</div>
<div class="margin-top" data-service="database.listCollections" data-event="load,database.createCollection,database.updateCollection,database.deleteCollection" data-scope="sdk" data-param-limit="100" data-name="project-collections">
<div class="margin-top" data-service="databases.listCollections" data-param-database-id="{{router.params.databaseId}}" data-event="load,databases.createCollection,databases.updateCollection,databases.deleteCollection" data-scope="sdk" data-param-limit="100" data-name="project-collections">
</div>
<script type="text/html" id="template-attribute-title-first">
+326
View File
@@ -0,0 +1,326 @@
<div
data-service="databases.get"
data-param-database-id="{{router.params.id}}"
data-scope="sdk"
data-event="load,databases.update"
data-name="project-database">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Database</a>
<br />
<span data-ls-bind="{{project-database.name}}">&nbsp;&nbsp;</span>
</h1>
</div>
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>JSON View</h2>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{project-database}}" data-forms-code />
</div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/databases/database?id={{router.params.id}}&project={{router.params.project}}">
<h2>Collections</h2>
<div class="margin-top"
data-service="databases.listCollections"
data-event="load,databases.createCollection,databases.updateCollection,databases.deleteCollection"
data-param-database-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="{{router.params.offset}}"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections">
<div data-ls-if="(!{{project-collections.total}})" class="box dashboard margin-bottom">
<div class="margin-bottom-small margin-top-small margin-end margin-start">
<h3 class="margin-bottom-small text-bold">No Collections Found</h3>
<p class="margin-bottom-no">You haven't created any collections for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-collections.total}}">
<ul data-ls-loop="project-collections.collections" data-ls-as="collection" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/databases/collection?id={{collection.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{collection.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<i class="icon-right-open"></i>
</a>
</li>
</ul>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="databases.listCollections"
data-param-database-id="{{router.params.id}}"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-collections.total|pageTotal}}"></span>
<form
data-service="databases.listCollections"
data-param-database-id="{{router.params.id}}"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Collection</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database Collection"
data-service="databases.createCollection"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Collection created successfully"
data-success-param-redirect-url="/console/databases/collection/settings?id={{serviceData.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.createCollection"
data-failure="alert"
data-failure-param-alert-text="Failed to create collection"
data-failure-param-alert-classname="error">
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.id}}" />
<label for="collection-id">Collection ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="databases.getCollection"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="collectionId" />
<label for="collection-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-permission" name="permission" required value="collection" />
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</li>
<li data-state="/console/databases/database/usage?project={{router.params.project}}&id={{router.params.id}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="databases.getDatabaseUsage"
data-event="submit"
data-name="usage"
data-param-database-id="{{router.params.id}}"
data-param-range="90d">
<button class="tick">90d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="databases.getDatabaseUsage"
data-param-database-id="{{router.params.id}}"
data-event="submit"
data-name="usage">
<button class="tick">30d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="databases.getDatabaseUsage"
data-event="submit"
data-name="usage"
data-param-database-id="{{router.params.id}}"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<h2>Usage</h2>
<div
data-service="databases.getDatabaseUsage"
data-param-database-id="{{router.params.id}}"
data-event="load"
data-name="usage">
<h3 class="margin-bottom-tiny">Objects</h3>
<p class="text-fade">Count of collections and documents over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input
type="hidden"
data-ls-bind="{{usage}}"
data-forms-chart="Collections=collectionsCount,Documents=documentsCount"
data-show-y-axis="true"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
</ul>
<h3 class="margin-bottom-tiny">Collections</h3>
<p class="text-fade">Count of collections create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input
type="hidden"
data-ls-bind="{{usage}}"
data-forms-chart="Created=collectionsCreate,Read=collectionsRead,Updated=collectionsUpdate,Deleted=collectionsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
<h3 class="margin-bottom-tiny">Documents</h3>
<p class="text-fade">Count of documents create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input
type="hidden"
data-ls-bind="{{usage}}"
data-forms-chart="Created=documentsCreate,Read=documentsRead,Updated=documentsUpdate,Deleted=documentsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
</div>
</li>
<li data-state="/console/databases/database/settings?id={{router.params.id}}&project={{router.params.project}}">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Database"
data-service="databases.update"
data-scope="sdk"
data-event="submit"
data-param-database-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated database successfully"
data-success-param-trigger-events="databases.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update database"
data-failure-param-alert-classname="error">
<label>&nbsp;</label>
<div class="box">
<label for="database-name">Name</label>
<input name="name" id="database-name" type="text" autocomplete="off" data-ls-bind="{{project-database.name}}" data-forms-text-direction required placeholder="Database Name" maxlength="128" />
<hr class="margin-top-no" />
<button>Update</button>
</form>
</div>
</div>
<div class="col span-4 sticky-top">
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-database.$id}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-database.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-database.$createdAt|dateText}}"></span></li>
</ul>
<form
name="databases.delete" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Database"
data-service="databases.delete"
data-event="submit"
data-param-database-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this Database?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Database deleted successfully"
data-success-param-trigger-events="databases.delete"
data-success-param-redirect-url="/console/databases?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete database"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Database</button>
</form>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
@@ -5,15 +5,17 @@ $logs = $this->getParam('logs', null);
?>
<div
data-service="database.getCollection"
data-service="databases.getCollection"
data-param-collection-id="{{router.params.collection}}"
data-param-database-id="{{router.params.databaseId}}"
data-scope="sdk"
data-event="load,database.updateDocument"
data-event="load,databases.updateDocument"
data-name="project-collection">
<div
data-service="database.getDocument"
data-service="databases.getDocument"
data-param-collection-id="{{router.params.collection}}"
data-param-database-id="{{router.params.databaseId}}"
data-param-document-id="{{router.params.id}}"
data-scope="sdk"
data-event="load"
@@ -22,7 +24,7 @@ $logs = $this->getParam('logs', null);
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/database/collection?id={{router.params.collection}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-collection.name}}"></span></a>
<a data-ls-attrs="href=/console/databases/collection?id={{router.params.collection}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-collection.name}}"></span></a>
<br />
@@ -45,7 +47,7 @@ $logs = $this->getParam('logs', null);
<div class="zone xl margin-bottom-no">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/database/document?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
<li data-state="/console/databases/document?id={{router.params.id}}&collection={{router.params.collection}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2 class="margin-bottom">Overview</h2>
<div class="row responsive">
@@ -64,19 +66,20 @@ $logs = $this->getParam('logs', null);
<?php if($new): ?>
data-analytics-label="Create Database Document"
data-success="trigger,redirect"
data-success-param-trigger-events="database.createDocument"
data-success-param-redirect-url="/console/database/collection?id={{project-collection.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.createDocument"
data-success-param-redirect-url="/console/databases/collection?id={{project-collection.$id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}"
data-failure-param-alert-text="Failed to create document"
<?php else: ?>
data-analytics-label="Update Database Document"
data-success="trigger,alert"
data-success-param-trigger-events="database.updateDocument"
data-success-param-trigger-events="databases.updateDocument"
data-success-param-alert-text="Your document was updated"
data-failure-param-alert-text="Failed to update document"
<?php endif; ?>
>
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<?php if(!$new): ?><input type="hidden" name="documentId" data-ls-bind="{{project-document.$id}}" /><?php endif; ?>
<div class="box">
@@ -86,7 +89,7 @@ $logs = $this->getParam('logs', null);
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="database.getDocument"
data-validator="databases.getDocument"
required
maxlength="36"
name="documentId"
@@ -346,27 +349,37 @@ $logs = $this->getParam('logs', null);
</div>
<ul class="margin-bottom-large text-fade text-size-small" data-ls-if="({{project-document.$id}})">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Document)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-document.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-document.$createdAt|dateText}}"></span></li>
</ul>
<div data-ls-if="({{project-document.$id}})">
<form name="database.deleteDocument" class="margin-bottom"
<form name="databases.deleteDocument" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Document"
data-service="database.deleteDocument"
data-service="databases.deleteDocument"
data-event="submit"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.collection}}"
data-param-document-id="{{project-document.$id}}"
data-confirm="Are you sure you want to delete this document?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Document deleted successfully"
data-success-param-trigger-events="database.deleteDocument"
data-success-param-redirect-url="/console/database/collection?id={{router.params.collection}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.deleteDocument"
data-success-param-redirect-url="/console/databases/collection?id={{router.params.collection}}&project={{router.params.project}}&databaseId={{router.params.databaseId}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete collection"
data-failure-param-alert-classname="error">
@@ -378,7 +391,7 @@ $logs = $this->getParam('logs', null);
</div>
</li>
<?php if(!$new): ?>
<li data-state="/console/database/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}}">
<h2>Activity</h2>
<?php echo $logs->render(); ?>
@@ -9,32 +9,32 @@
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/database?project={{router.params.project}}">
<h2>Collections</h2>
<li data-state="/console/databases?project={{router.params.project}}">
<h2>Databases</h2>
<div class="margin-top"
data-service="database.listCollections"
data-event="load,database.createCollection,database.updateCollection,database.deleteCollection"
data-service="databases.list"
data-event="load,databases.create,databases.update,databases.delete"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="{{router.params.offset}}"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections">
data-name="project-databases">
<div data-ls-if="(!{{project-collections.total}})" class="box dashboard margin-bottom">
<div data-ls-if="(!{{project-databases.total}})" class="box dashboard margin-bottom">
<div class="margin-bottom-small margin-top-small margin-end margin-start">
<h3 class="margin-bottom-small text-bold">No Collections Found</h3>
<h3 class="margin-bottom-small text-bold">No Databases Found</h3>
<p class="margin-bottom-no">You haven't created any collections for your project yet.</p>
<p class="margin-bottom-no">You haven't created any databases for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-collections.total}}">
<ul data-ls-loop="project-collections.collections" data-ls-as="collection" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<div data-ls-if="0 != {{project-databases.total}}">
<ul data-ls-loop="project-databases.databases" data-ls-as="database" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/database/collection?id={{collection.$id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{collection.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<a data-ls-attrs="href=/console/databases/database?id={{database.$id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{database.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<i class="icon-right-open"></i>
</a>
@@ -44,84 +44,79 @@
<div class="pull-end text-align-center paging">
<form
data-service="database.listCollections"
data-service="databases.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections"
data-name="project-databases"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-collections.total|pageTotal}}"></span>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-databases.total|pageTotal}}"></span>
<form
data-service="database.listCollections"
data-service="databases.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="ASC"
data-scope="sdk"
data-name="project-collections"
data-name="project-databases"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Database">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Collection</h1>
<h1>New Database</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database Collection"
data-service="database.createCollection"
data-analytics-label="Create Database"
data-service="databases.create"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Collection created successfully"
data-success-param-redirect-url="/console/database/collection/settings?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="database.createCollection"
data-success-param-alert-text="Database created successfully"
data-success-param-redirect-url="/console/databases/database?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create collection"
data-failure-param-alert-text="Failed to create database"
data-failure-param-alert-classname="error">
<label for="collection-id">Collection ID</label>
<label for="database-id">Database ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="database.getCollection"
data-validator="databases.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="collectionId" />
<label for="collection-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-permission" name="permission" required value="collection" />
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
name="databaseId" />
<label for="database-name">Name</label>
<input type="text" class="full-width" id="database-name" name="name" required autocomplete="off" maxlength="128" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</li>
<li data-state="/console/database/usage?project={{router.params.project}}">
</li><!-- TODO need to work on usage -->
<li data-state="/console/databases/usage?project={{router.params.project}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="database.getUsage"
data-service="databases.getUsage"
data-event="submit"
data-name="usage"
data-param-range="90d">
@@ -131,7 +126,7 @@
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="database.getUsage"
data-service="databases.getUsage"
data-event="submit"
data-name="usage">
<button class="tick">30d</button>
@@ -140,7 +135,7 @@
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="database.getUsage"
data-service="databases.getUsage"
data-event="submit"
data-name="usage"
data-param-range="24h">
@@ -152,7 +147,7 @@
<h2>Usage</h2>
<div
data-service="database.getUsage"
data-service="databases.getUsage"
data-event="load"
data-name="usage">
<h3 class="margin-bottom-tiny">Objects</h3>
@@ -163,7 +158,7 @@
<input
type="hidden"
data-ls-bind="{{usage}}"
data-forms-chart="Collections=collectionsCount,Documents=documentsCount"
data-forms-chart="Databases=databasesCount,Collections=collectionsCount,Documents=documentsCount"
data-show-y-axis="true"
data-height="140" />
</div>
@@ -171,10 +166,33 @@
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Databases <span data-ls-bind="({{usage.databasesCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
</ul>
<h3 class="margin-bottom-tiny">Databases</h3>
<p class="text-fade">Count of databases create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input
type="hidden"
data-ls-bind="{{usage}}"
data-forms-chart="Created=databasesCreate,Read=databasesRead,Updated=databasesUpdate,Deleted=databasesDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
<h3 class="margin-bottom-tiny">Collections</h3>
<p class="text-fade">Count of collections create, read, update and delete operations over time</p>
+31 -3
View File
@@ -4,7 +4,12 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
$events = $this->getParam('events', []);
$timeout = $this->getParam('timeout', 900);
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
$patterns = [];
$patterns = [
'documents',
'documents.create',
'documents.update',
'documents.delete',
];
foreach ($events as $name => $event) {
$patterns[] = $name;
@@ -245,7 +250,16 @@ sort($patterns);
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Function)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
</ul>
@@ -595,7 +609,16 @@ sort($patterns);
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Function)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
</ul>
@@ -644,6 +667,11 @@ sort($patterns);
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubSubResource">
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
+10 -1
View File
@@ -471,7 +471,16 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Bucket)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.$updatedAt|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.$createdAt|dateText}}"></span></li>
</ul>
+6 -6
View File
@@ -80,19 +80,19 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<td data-title="Name: ">
<a data-ls-attrs="href=/console/users/user?id={{user.$id}}&project={{router.params.project}}">
<span data-ls-bind="{{user.name}}" data-ls-attrs="title={{user.name}}"></span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} !== ''">Unknown</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === ''">Anonymous User</span>
<span data-ls-if="{{user.name|escape}} === '' && ({{user.email}} !== '' || {{user.phone}} !== '')">Unknown</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === '' && {{user.phone}} === ''">Anonymous User</span>
</a>
</td>
<td data-title="Email: ">
<small data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
<span data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
</td>
<td data-title="Status: ">
<span data-ls-if="{{user.emailVerification}} === true && {{user.status}} === true">
<span data-ls-if="({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
<span class="tag green">Verified</span>
</span>
<span data-ls-if="{{user.emailVerification}} !== true && {{user.status}} === true">
<span data-ls-if="!({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
<span class="tag">Unverified</span>
</span>
@@ -453,7 +453,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project OAuth2"
data-analytics-label="Update Project OAuth2 (<?php echo $this->escape($name); ?>)"
data-service="projects.updateOAuth2"
data-scope="console"
data-event="submit"
@@ -0,0 +1,12 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">App ID</label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret}}" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Endpoint">Endpoint (optional)<span class="tooltip" data-tooltip="If you have self-hosted instance, provide the host."><i class="icon-info-circled"></i></span></label>
<input name="endpoint" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Endpoint" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Endpoint}}" placeholder="https://gitlab.com" />
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
+10 -1
View File
@@ -206,7 +206,16 @@
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Team)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.$createdAt|dateText}}"></span></li>
</ul>
+10 -1
View File
@@ -286,7 +286,16 @@
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-email" class="link text-size-small">Update Email</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-phone" class="link text-size-small">Update Phone</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-password" class="link text-size-small">Update Password</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
class="link text-size-small"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (User)">
View as JSON
</button>
</li>
</ul>
<div data-ls-if="{{user.email}} && {{user.emailVerification}} === false" style="display: none">
+59 -6
View File
@@ -1,7 +1,12 @@
<?php
$new = $this->getParam('new', false);
$events = $this->getParam('events', []);
$patterns = [];
$patterns = [
'documents',
'documents.create',
'documents.update',
'documents.delete',
];
foreach ($events as $name => $event) {
$patterns[] = $name;
@@ -30,7 +35,7 @@ sort($patterns);
data-service="projects.getWebhook"
data-name="project-webhook"
data-scope="console"
data-event="load,projects.createWebhook, projects.deleteWebhook, projects.updateWebhook"
data-event="load,projects.createWebhook,projects.deleteWebhook,projects.updateWebhook,projects.updateWebhookSignature"
data-param-project-id="{{router.params.project}}"
data-param-webhook-id="{{router.params.id}}"
data-success="trigger"
@@ -91,9 +96,6 @@ sort($patterns);
<label data-ls-attrs="for=url-{{webhook.$id}}">POST URL</label>
<input type="url" class="full-width" data-ls-attrs="id=url-{{project-webhook.$id}}" name="url" required autocomplete="off" placeholder="https://example.com/callback" data-ls-bind="{{project-webhook.url}}" />
<label data-ls-attrs="for=signatureKey-{{webhook.$id}}">Signature Key</label>
<input type="text" class="full-width" data-ls-attrs="id=signatureKey-{{project-webhook.$id}}" name="signatureKey" required autocomplete="off" placeholder="ad3d581ca230e2b7059c545e5a" data-ls-bind="{{project-webhook.signatureKey}}" />
<section class="margin-bottom-small" data-ls-attrs="x-init=load({{project-webhook.events}})">
<label class="margin-bottom-small">Events <span class="tooltip small" data-tooltip="Set events that will trigger your webhook."><i class="icon-info-circled"></i></span></label>
<div>
@@ -159,7 +161,53 @@ sort($patterns);
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.$id}}" disabled data-forms-copy>
</div>
<form class="margin-bottom" data-analytics data-analytics-activity data-analytics-event="submit" data-analytics-category="console" data-analytics-label="Delete Project Webhook" data-service="projects.deleteWebhook" data-scope="console" data-event="submit" data-confirm="Are you sure you want to delete this webhook?" data-success="alert,redirect" data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}" data-success-param-alert-text="Deleted webhook successfully" data-success-param-trigger-events="projects.deleteWebhook" data-failure="alert" data-failure-param-alert-text="Failed to delete webhook" data-failure-param-alert-classname="error">
<label>Signature Key <span class="tooltip small" data-tooltip="Can be used to validate your Webhooks."><i class="icon-info-circled"></i></span></label>
<div class="input-copy margin-bottom">
<input id="signatureKey" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.signatureKey}}" disabled data-forms-copy>
</div>
<form
class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Webhook Signature"
data-service="projects.updateWebhookSignature"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to generate a new Signature Key?"
data-success="trigger"
data-success-param-alert-text="Updated webhook signature key successfully"
data-success-param-trigger-events="projects.updateWebhookSignature"
data-failure="alert"
data-failure-param-alert-text="Failed to update webhook signature key"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
<button class="fill">Update Signature Key</button>
</form>
<form
class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Webhook"
data-service="projects.deleteWebhook"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to delete this webhook?"
data-success="alert,redirect"
data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}"
data-success-param-alert-text="Deleted webhook successfully"
data-success-param-trigger-events="projects.deleteWebhook"
data-failure="alert"
data-failure-param-alert-text="Failed to delete webhook"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
@@ -190,6 +238,11 @@ sort($patterns);
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubSubResource">
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
+30 -3
View File
@@ -149,6 +149,8 @@ services:
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_PHONE_PROVIDER
- _APP_PHONE_SECRET
appwrite-realtime:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@@ -295,11 +297,11 @@ services:
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
appwrite-worker-database:
appwrite-worker-databases:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-database
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-database
container_name: appwrite-worker-databases
restart: unless-stopped
networks:
- appwrite
@@ -497,6 +499,26 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-messaging:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_PHONE_PROVIDER
- _APP_PHONE_FROM
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-maintenance:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: maintenance
@@ -593,6 +615,11 @@ services:
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
networks:
- appwrite
volumes:
@@ -23,6 +23,7 @@ class DatabaseV1 extends Worker
$project = new Document($this->args['project']);
$collection = new Document($this->args['collection'] ?? []);
$document = new Document($this->args['document'] ?? []);
$database = new Document($this->args['database'] ?? []);
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -34,16 +35,16 @@ class DatabaseV1 extends Worker
switch (strval($type)) {
case DATABASE_TYPE_CREATE_ATTRIBUTE:
$this->createAttribute($collection, $document, $project->getId());
$this->createAttribute($database, $collection, $document, $project->getId());
break;
case DATABASE_TYPE_DELETE_ATTRIBUTE:
$this->deleteAttribute($collection, $document, $project->getId());
$this->deleteAttribute($database, $collection, $document, $project->getId());
break;
case DATABASE_TYPE_CREATE_INDEX:
$this->createIndex($collection, $document, $project->getId());
$this->createIndex($database, $collection, $document, $project->getId());
break;
case DATABASE_TYPE_DELETE_INDEX:
$this->deleteIndex($collection, $document, $project->getId());
$this->deleteIndex($database, $collection, $document, $project->getId());
break;
default:
@@ -57,16 +58,18 @@ class DatabaseV1 extends Worker
}
/**
* @param Document $database
* @param Document $collection
* @param Document $attribute
* @param string $projectId
*/
protected function createAttribute(Document $collection, Document $attribute, string $projectId): void
protected function createAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForProject = $this->getProjectDB($projectId);
$events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].update', [
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
@@ -89,7 +92,7 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if (!$dbForProject->createAttribute('collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Attribute');
}
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
@@ -101,7 +104,7 @@ class DatabaseV1 extends Worker
// Pass first, most verbose event pattern
event: $events[0],
payload: $attribute,
project: $project
project: $project,
);
Realtime::send(
@@ -112,25 +115,28 @@ class DatabaseV1 extends Worker
roles: $target['roles'],
options: [
'projectId' => $projectId,
'databaseId' => $database->getId(),
'collectionId' => $collection->getId()
]
);
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
}
/**
* @param Document $database
* @param Document $collection
* @param Document $attribute
* @param string $projectId
*/
protected function deleteAttribute(Document $collection, Document $attribute, string $projectId): void
protected function deleteAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForProject = $this->getProjectDB($projectId);
$events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].delete', [
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
@@ -146,7 +152,7 @@ class DatabaseV1 extends Worker
// - failed: attribute was never created
// - stuck: attribute was available but cannot be removed
try {
if ($status !== 'failed' && !$dbForProject->deleteAttribute('collection_' . $collection->getInternalId(), $key)) {
if ($status !== 'failed' && !$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete Attribute');
}
$dbForProject->deleteDocument('attributes', $attribute->getId());
@@ -169,6 +175,7 @@ class DatabaseV1 extends Worker
roles: $target['roles'],
options: [
'projectId' => $projectId,
'databaseId' => $database->getId(),
'collectionId' => $collection->getId()
]
);
@@ -218,7 +225,7 @@ class DatabaseV1 extends Worker
}
if ($exists) { // Delete the duplicate if created, else update in db
$this->deleteIndex($collection, $index, $projectId);
$this->deleteIndex($database, $collection, $index, $projectId);
} else {
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
@@ -226,21 +233,23 @@ class DatabaseV1 extends Worker
}
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId());
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
}
/**
* @param Document $database
* @param Document $collection
* @param Document $index
* @param string $projectId
*/
protected function createIndex(Document $collection, Document $index, string $projectId): void
protected function createIndex(Document $database, Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForProject = $this->getProjectDB($projectId);
$events = Event::generateEvents('collections.[collectionId].indexes.[indexId].update', [
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
@@ -253,7 +262,7 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if (!$dbForProject->createIndex('collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
throw new Exception('Failed to create Index');
}
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
@@ -276,25 +285,28 @@ class DatabaseV1 extends Worker
roles: $target['roles'],
options: [
'projectId' => $projectId,
'databaseId' => $database->getId(),
'collectionId' => $collection->getId()
]
);
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
}
/**
* @param Document $database
* @param Document $collection
* @param Document $index
* @param string $projectId
*/
protected function deleteIndex(Document $collection, Document $index, string $projectId): void
protected function deleteIndex(Document $database, Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForProject = $this->getProjectDB($projectId);
$events = Event::generateEvents('collections.[collectionId].indexes.[indexId].delete', [
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
@@ -303,7 +315,7 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if ($status !== 'failed' && !$dbForProject->deleteIndex('collection_' . $collection->getInternalId(), $key)) {
if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete index');
}
$dbForProject->deleteDocument('indexes', $index->getId());
@@ -326,11 +338,12 @@ class DatabaseV1 extends Worker
roles: $target['roles'],
options: [
'projectId' => $projectId,
'databaseId' => $database->getId(),
'collectionId' => $collection->getId()
]
);
}
$dbForProject->deleteCachedDocument('collections', $collection->getId());
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collection->getId());
}
}
+26 -3
View File
@@ -44,6 +44,9 @@ class DeletesV1 extends Worker
$document = new Document($this->args['document'] ?? []);
switch ($document->getCollection()) {
case DELETE_TYPE_DATABASES:
$this->deleteDatabase($document, $project->getId());
break;
case DELETE_TYPE_COLLECTIONS:
$this->deleteCollection($document, $project->getId());
break;
@@ -76,8 +79,8 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_AUDIT:
$timestamp = $payload['timestamp'] ?? 0;
$document = new Document($payload['document'] ?? []);
$timestamp = $this->args['timestamp'] ?? 0;
$document = new Document($this->args['document'] ?? []);
if (!empty($timestamp)) {
$this->deleteAuditLogs($this->args['timestamp']);
@@ -119,6 +122,25 @@ class DeletesV1 extends Worker
{
}
/**
* @param Document $document database document
* @param string $projectId
*/
protected function deleteDatabase(Document $document, string $projectId): void
{
$databaseId = $document->getId();
$dbForProject = $this->getProjectDB($projectId);
$this->deleteByGroup('database_' . $document->getInternalId(), [], $dbForProject, function ($document) use ($projectId) {
$this->deleteCollection($document, $projectId);
});
$dbForProject->deleteCollection('database_' . $document->getInternalId());
$this->deleteAuditLogsByResource('database/' . $databaseId, $projectId);
}
/**
* @param Document $document teams document
* @param string $projectId
@@ -126,10 +148,11 @@ class DeletesV1 extends Worker
protected function deleteCollection(Document $document, string $projectId): void
{
$collectionId = $document->getId();
$databaseId = str_replace('database_', '', $document->getCollection());
$dbForProject = $this->getProjectDB($projectId);
$dbForProject->deleteCollection('collection_' . $document->getInternalId());
$dbForProject->deleteCollection('database_' . $databaseId . '_collection_' . $document->getInternalId());
$this->deleteByGroup('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
+11
View File
@@ -5,6 +5,8 @@ use Appwrite\Auth\Phone\Mock;
use Appwrite\Auth\Phone\Telesign;
use Appwrite\Auth\Phone\TextMagic;
use Appwrite\Auth\Phone\Twilio;
use Appwrite\Auth\Phone\Msg91;
use Appwrite\Auth\Phone\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Resque\Worker;
use Utopia\App;
@@ -36,8 +38,12 @@ class MessagingV1 extends Worker
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),
'msg91' => new Msg91($user, $secret),
'vonage' => new Vonage($user, $secret),
default => null
};
$this->from = App::getEnv('_APP_PHONE_FROM');
}
public function run(): void
@@ -47,6 +53,11 @@ class MessagingV1 extends Worker
return;
}
if (empty($this->from)) {
Console::info('Skipped sms processing. No phone number has been set.');
return;
}
$recipient = $this->args['recipient'];
$message = $this->args['message'];