1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00

Merge branch 'feat-database-indexing' into feat-storage-buckets

This commit is contained in:
Damodar Lohani 2021-12-02 13:53:17 +05:45
commit b22797318e
25 changed files with 3869 additions and 3863 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1186,7 +1186,7 @@ App::get('/v1/account/logs')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->inject('response')
->inject('user')
->inject('locale')

View file

@ -26,6 +26,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\Structure as StructureException;
use Appwrite\Auth\Auth;
use Appwrite\Database\Validator\CustomId;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
@ -206,7 +207,7 @@ App::get('/v1/database/collections')
->label('sdk.response.model', Response::MODEL_COLLECTION_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 collection used as the starting point for the query, excluding the collection 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('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
@ -499,7 +500,7 @@ App::get('/v1/database/collections/:collectionId/logs')
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('collectionId', '', new UID(), 'Collection unique ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
@ -1639,6 +1640,19 @@ App::post('/v1/database/collections/:collectionId/documents')
$data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user
$data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user
// Users can only add their roles to documents, API keys can add any
$roles = \array_fill_keys(Authorization::getRoles(), true); // Auth::isAppUser expects roles to be keys, not values of assoc array
foreach ($data['$read'] as $read) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
foreach ($data['$write'] as $write) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
try {
if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */
@ -1685,7 +1699,7 @@ App::get('/v1/database/collections/:collectionId/documents')
->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).')
->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document 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('orderAttributes', [], new ArrayList(new Text(128)), 'Array of attributes used to sort results.', true)
@ -1825,7 +1839,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId/logs')
->param('collectionId', '', new UID(), 'Collection unique ID.')
->param('documentId', null, new UID(), 'Document unique ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
@ -1984,6 +1998,19 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
$data['$read'] = (is_null($read)) ? ($document->getRead() ?? []) : $read; // By default inherit read permissions
$data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions
// Users can only add their roles to documents, API keys can add any
$roles = \array_fill_keys(Authorization::getRoles(), true); // Auth::isAppUser expects roles to be keys, not values of assoc array
foreach ($data['$read'] as $read) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
foreach ($data['$write'] as $write) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
try {
if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */

View file

@ -89,7 +89,7 @@ App::get('/v1/functions')
->label('sdk.response.model', Response::MODEL_FUNCTION_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
@ -508,7 +508,7 @@ App::get('/v1/functions/:functionId/tags')
->param('functionId', '', new UID(), 'Function unique ID.')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 tag used as the starting point for the query, excluding the tag 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('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
@ -769,7 +769,7 @@ App::get('/v1/functions/:functionId/executions')
->label('sdk.response.model', Response::MODEL_EXECUTION_LIST)
->param('functionId', '', new UID(), 'Function unique ID.')
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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('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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)

View file

@ -59,13 +59,11 @@ App::post('/v1/projects')
->inject('dbForConsole')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('consoleDB')
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal, $consoleDB) {
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Database\Database $consoleDB */
$team = $dbForConsole->getDocument('teams', $teamId);
@ -106,7 +104,7 @@ App::post('/v1/projects')
'search' => implode(' ', [$projectId, $name]),
]));
$collections = Config::getParam('collections2', []); /** @var array $collections */
$collections = Config::getParam('collections', []); /** @var array $collections */
$dbForInternal->setNamespace('project_' . $project->getId() . '_internal');
$dbForInternal->create();
@ -148,8 +146,6 @@ App::post('/v1/projects')
$dbForInternal->createCollection($key, $attributes, $indexes);
}
$consoleDB->createNamespace($project->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($project, Response::MODEL_PROJECT);
});
@ -166,7 +162,7 @@ App::get('/v1/projects')
->label('sdk.response.model', Response::MODEL_PROJECT_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)

View file

@ -687,7 +687,7 @@ App::get('/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('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)

View file

@ -106,7 +106,7 @@ App::get('/v1/teams')
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
@ -444,7 +444,7 @@ App::get('/v1/teams/:teamId/memberships')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)

View file

@ -94,7 +94,7 @@ App::get('/v1/users')
->label('sdk.response.model', Response::MODEL_USER_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', 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 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.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
@ -260,7 +260,7 @@ App::get('/v1/users/:userId/logs')
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('userId', '', new UID(), 'User unique ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination.', true)
->inject('response')
->inject('dbForInternal')
->inject('locale')

View file

@ -90,7 +90,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
if(!$dbForConsole->exists()) {
Console::success('[Setup] - Server database init started...');
$collections = Config::getParam('collections2', []); /** @var array $collections */
$collections = Config::getParam('collections', []); /** @var array $collections */
$redis->flushAll();

View file

@ -132,7 +132,6 @@ Config::load('auth', __DIR__.'/config/auth.php');
Config::load('providers', __DIR__.'/config/providers.php');
Config::load('platforms', __DIR__.'/config/platforms.php');
Config::load('collections', __DIR__.'/config/collections.php');
Config::load('collections2', __DIR__.'/config/collections2.php');
Config::load('runtimes', __DIR__.'/config/runtimes.php');
Config::load('roles', __DIR__.'/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__.'/config/scopes.php'); // User roles and scopes
@ -809,24 +808,6 @@ App::setResource('console', function() {
]);
}, []);
App::setResource('consoleDB', function($db, $cache) {
$consoleDB = new DatabaseOld();
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects
$consoleDB->setMocks(Config::getParam('collections', []));
return $consoleDB;
}, ['db', 'cache']);
App::setResource('projectDB', function($db, $cache, $project) {
$projectDB = new DatabaseOld();
$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$projectDB->setNamespace('app_'.$project->getId());
$projectDB->setMocks(Config::getParam('collections', []));
return $projectDB;
}, ['db', 'cache', 'project']);
App::setResource('dbForInternal', function($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));

View file

@ -11,6 +11,8 @@ use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Migration\Migration;
use Utopia\Validator\Text;
Config::load('collections.old', __DIR__.'/../config/collections.old.php');
$cli
->task('migrate')
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
@ -29,12 +31,12 @@ $cli
$consoleDB
->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache))
->setNamespace('app_console') // Main DB
->setMocks(Config::getParam('collections', []));
->setMocks(Config::getParam('collections.old', []));
$projectDB = new Database();
$projectDB
->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache))
->setMocks(Config::getParam('collections', []));
->setMocks(Config::getParam('collections.old', []));
$console = $consoleDB->getDocument('console');

View file

@ -78,11 +78,11 @@ $logs = $this->getParam('logs', null);
<tbody data-ls-attrs="x-init=documents = {{project-documents.documents}}" x-data="{documents: []}">
<template x-for="doc in documents">
<tr>
<td>
<td data-title="$id: ">
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`" x-text="doc.$id"></a>
</td>
<template x-for="attr in attributes">
<td x-show="attr.status === 'available'" style="width: 170px">
<td x-show="attr.status === 'available'" :data-title="attr.key + ':'">
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`">
<span x-text="doc[attr.key] ?? 'n/a'"></span>
</a>
@ -192,7 +192,7 @@ $logs = $this->getParam('logs', null);
<td data-title="Default:">
<span class="text-size-small" data-ls-bind="{{attribute.default}}" data-ls-attr="title={{attribute.default}}"></span>
<span class="text-fade text-size-small" data-ls-if="!({{attribute.default}})">n/a</span>
<span class="text-fade text-size-small" data-ls-if="{{attribute.default}} != ''">n/a</span>
</td>
<td data-title="">

View file

@ -234,11 +234,11 @@ $logs = $this->getParam('logs', null);
</script>
<script type="text/html" id="template-boolean">
<input type="hidden" data-forms-switch data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" data-cast-to="boolean" class="margin-bottom-no" />
<input type="hidden" data-forms-switch data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" data-cast-to="boolean" />
</script>
<script type="text/html" id="template-boolean-array">
<input type="hidden" data-forms-switch data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{item}}" data-cast-to="boolean" class="margin-bottom-no" />
<input type="hidden" data-forms-switch data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{item}}" data-cast-to="boolean" />
</script>
<script type="text/html" id="template-url">

View file

@ -3433,8 +3433,9 @@ const highest=history.reduce((prev,curr)=>{return(curr.value>prev)?curr.value:pr
newHistory[project]=history;}
let currentSnapshot={...current};for(let index=.1;index<=1;index+=.05){let currentTransition={...currentSnapshot};for(const project in current){if(project in newHistory){let base=newHistory[project][bars-2].value;let cur=currentSnapshot[project];let offset=(cur-base)*index;currentTransition[project]=base+Math.floor(offset);}}
realtime.setCurrent(currentTransition);await sleep(250);}
realtime.setHistory(newHistory);}});window.formValidation=(form,fields)=>{const elements=form.elements;const actionHandler=(action,attribute)=>{switch(action){case"disable":elements[attribute].setAttribute("disabled",true);elements[attribute].dispatchEvent(new Event('change'));break;case"enable":elements[attribute].removeAttribute("disabled");elements[attribute].dispatchEvent(new Event('change'));break;case"unvalue":elements[attribute].value="";break;case"check":elements[attribute].value="true";break;case"uncheck":elements[attribute].value="false";break;}};for(const field in fields){for(const attribute in fields[field]){const attr=fields[field][attribute];if(Array.isArray(attr)){attr.forEach(action=>{if(elements[field].value==="true"){actionHandler(action,attribute);}})}else{const condition=attr.if.some(c=>{return elements[c].value==="true";});if(condition){for(const thenAction in attr.then){attr.then[thenAction].forEach(action=>{actionHandler(action,thenAction);});}}else{for(const elseAction in attr.else){attr.else[elseAction].forEach(action=>{actionHandler(action,elseAction);});}}}}}
form.addEventListener("reset",()=>{for(const key in fields){if(Object.hasOwnProperty.call(fields,key)){const element=form.elements[key];element.setAttribute("value","");element.removeAttribute("disabled");element.dispatchEvent(new Event("change"));}}});};(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
realtime.setHistory(newHistory);}});window.formValidation=(form,fields)=>{const elements=Array.from(form.querySelectorAll('[name]')).reduce((prev,curr)=>{if(!curr.name){return prev;}
prev[curr.name]=curr;return prev;},{});const actionHandler=(action,attribute)=>{switch(action){case"disable":elements[attribute].setAttribute("disabled",true);elements[attribute].dispatchEvent(new Event('change'));break;case"enable":elements[attribute].removeAttribute("disabled");elements[attribute].dispatchEvent(new Event('change'));break;case"unvalue":elements[attribute].value="";break;case"check":elements[attribute].value="true";break;case"uncheck":elements[attribute].value="false";break;}};for(const field in fields){for(const attribute in fields[field]){const attr=fields[field][attribute];if(Array.isArray(attr)){attr.forEach(action=>{if(elements[field].value==="true"){actionHandler(action,attribute);}})}else{const condition=attr.if.some(c=>{return elements[c].value==="true";});if(condition){for(const thenAction in attr.then){attr.then[thenAction].forEach(action=>{actionHandler(action,thenAction);});}}else{for(const elseAction in attr.else){attr.else[elseAction].forEach(action=>{actionHandler(action,elseAction);});}}}}}
form.addEventListener("reset",()=>{for(const key in fields){if(Object.hasOwnProperty.call(fields,key)){const element=elements[key];element.setAttribute("value","");element.removeAttribute("disabled");element.dispatchEvent(new Event("change"));}}});};(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);}
return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();}
scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}
@ -3757,7 +3758,7 @@ if(selected&&list[selected].dataset["selected"]){let parent=element.querySelecto
if(!buttonAlias){buttonElements.forEach(button=>{button.innerText=buttonText;button.className=buttonClass;button.type=buttonElement;if(buttonIcon){let iconElement=document.createElement("i");iconElement.className=buttonIcon;button.insertBefore(iconElement,button.firstChild);}});}
if(buttonEvent){buttonElements.forEach(button=>{button.addEventListener("click",function(){document.dispatchEvent(new CustomEvent(buttonEvent,{bubbles:false,cancelable:true}));});});}
element.classList.add("modal");if(!buttonAlias&&!buttonHide){buttonElements.forEach(button=>{element.parentNode.insertBefore(button,element);});}
let open=function(){document.documentElement.classList.add("modal-open");document.dispatchEvent(new CustomEvent("modal-open",{bubbles:false,cancelable:true}));element.classList.add("open");element.classList.remove("close");let form=element.querySelector('form');let elements=(form&&form.elements)?[...form.elements]:[];for(let index=0;index<elements.length;index++){let element=elements[index];if(element.type!=='hidden'&&element.type!=='button'&&element.type!=='submit'&&!element.disabled){element.focus();break;}}};let close=function(event){document.documentElement.classList.remove("modal-open");element.classList.add("close");element.classList.remove("open");};if(name){document.querySelectorAll("[data-ui-modal-ref='"+name+"']").forEach(function(elem){elem.addEventListener("click",open);});}
let open=function(){document.documentElement.classList.add("modal-open");document.dispatchEvent(new CustomEvent("modal-open",{bubbles:false,cancelable:true}));element.classList.add("open");element.classList.remove("close");let form=element.querySelector('form');let elements=form?form.querySelectorAll('[name]'):[];for(const element of elements){if(element.type!=='hidden'&&element.type!=='button'&&element.type!=='submit'&&!element.disabled){element.focus();break;}}};let close=function(event){document.documentElement.classList.remove("modal-open");element.classList.add("close");element.classList.remove("open");};if(name){document.querySelectorAll("[data-ui-modal-ref='"+name+"']").forEach(function(elem){elem.addEventListener("click",open);});}
if(openEvent){document.addEventListener(openEvent,open);}
buttonElements.forEach(button=>{button.addEventListener("click",open);});document.addEventListener("keydown",function(event){if(event.which===27){close();}});element.addEventListener("blur",close);let closeButtons=element.querySelectorAll("[data-ui-modal-close]");for(let i=0;i<closeButtons.length;i++){closeButtons[i].addEventListener("click",close);}
document.addEventListener("modal-close",close);element.addEventListener(closeEvent,close);}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-ls-ui-open",controller:function(element,window){let def=element.classList.contains("open")?"open":"close";let buttonClass=element.dataset["buttonClass"]||"ls-ui-open";let buttonText=element.dataset["buttonText"]||"";let buttonIcon=element.dataset["buttonIcon"]||"";let buttonAria=element.dataset["buttonAria"]||"Open";let buttonSelector=element.dataset["buttonSelector"]||"";let hover=element.hasAttribute("data-hover");let blur=element.hasAttribute("data-blur");let button=window.document.createElement("button");let isTouch=function(){return("ontouchstart"in window||navigator.maxTouchPoints);};button.innerText=buttonText;button.className=buttonClass;button.type="button";if(buttonIcon){let icon=window.document.createElement("i");icon.className=buttonIcon;button.insertBefore(icon,button.firstChild);}

View file

@ -499,8 +499,9 @@ const highest=history.reduce((prev,curr)=>{return(curr.value>prev)?curr.value:pr
newHistory[project]=history;}
let currentSnapshot={...current};for(let index=.1;index<=1;index+=.05){let currentTransition={...currentSnapshot};for(const project in current){if(project in newHistory){let base=newHistory[project][bars-2].value;let cur=currentSnapshot[project];let offset=(cur-base)*index;currentTransition[project]=base+Math.floor(offset);}}
realtime.setCurrent(currentTransition);await sleep(250);}
realtime.setHistory(newHistory);}});window.formValidation=(form,fields)=>{const elements=form.elements;const actionHandler=(action,attribute)=>{switch(action){case"disable":elements[attribute].setAttribute("disabled",true);elements[attribute].dispatchEvent(new Event('change'));break;case"enable":elements[attribute].removeAttribute("disabled");elements[attribute].dispatchEvent(new Event('change'));break;case"unvalue":elements[attribute].value="";break;case"check":elements[attribute].value="true";break;case"uncheck":elements[attribute].value="false";break;}};for(const field in fields){for(const attribute in fields[field]){const attr=fields[field][attribute];if(Array.isArray(attr)){attr.forEach(action=>{if(elements[field].value==="true"){actionHandler(action,attribute);}})}else{const condition=attr.if.some(c=>{return elements[c].value==="true";});if(condition){for(const thenAction in attr.then){attr.then[thenAction].forEach(action=>{actionHandler(action,thenAction);});}}else{for(const elseAction in attr.else){attr.else[elseAction].forEach(action=>{actionHandler(action,elseAction);});}}}}}
form.addEventListener("reset",()=>{for(const key in fields){if(Object.hasOwnProperty.call(fields,key)){const element=form.elements[key];element.setAttribute("value","");element.removeAttribute("disabled");element.dispatchEvent(new Event("change"));}}});};(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
realtime.setHistory(newHistory);}});window.formValidation=(form,fields)=>{const elements=Array.from(form.querySelectorAll('[name]')).reduce((prev,curr)=>{if(!curr.name){return prev;}
prev[curr.name]=curr;return prev;},{});const actionHandler=(action,attribute)=>{switch(action){case"disable":elements[attribute].setAttribute("disabled",true);elements[attribute].dispatchEvent(new Event('change'));break;case"enable":elements[attribute].removeAttribute("disabled");elements[attribute].dispatchEvent(new Event('change'));break;case"unvalue":elements[attribute].value="";break;case"check":elements[attribute].value="true";break;case"uncheck":elements[attribute].value="false";break;}};for(const field in fields){for(const attribute in fields[field]){const attr=fields[field][attribute];if(Array.isArray(attr)){attr.forEach(action=>{if(elements[field].value==="true"){actionHandler(action,attribute);}})}else{const condition=attr.if.some(c=>{return elements[c].value==="true";});if(condition){for(const thenAction in attr.then){attr.then[thenAction].forEach(action=>{actionHandler(action,thenAction);});}}else{for(const elseAction in attr.else){attr.else[elseAction].forEach(action=>{actionHandler(action,elseAction);});}}}}}
form.addEventListener("reset",()=>{for(const key in fields){if(Object.hasOwnProperty.call(fields,key)){const element=elements[key];element.setAttribute("value","");element.removeAttribute("disabled");element.dispatchEvent(new Event("change"));}}});};(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);}
return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();}
scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}
@ -823,7 +824,7 @@ if(selected&&list[selected].dataset["selected"]){let parent=element.querySelecto
if(!buttonAlias){buttonElements.forEach(button=>{button.innerText=buttonText;button.className=buttonClass;button.type=buttonElement;if(buttonIcon){let iconElement=document.createElement("i");iconElement.className=buttonIcon;button.insertBefore(iconElement,button.firstChild);}});}
if(buttonEvent){buttonElements.forEach(button=>{button.addEventListener("click",function(){document.dispatchEvent(new CustomEvent(buttonEvent,{bubbles:false,cancelable:true}));});});}
element.classList.add("modal");if(!buttonAlias&&!buttonHide){buttonElements.forEach(button=>{element.parentNode.insertBefore(button,element);});}
let open=function(){document.documentElement.classList.add("modal-open");document.dispatchEvent(new CustomEvent("modal-open",{bubbles:false,cancelable:true}));element.classList.add("open");element.classList.remove("close");let form=element.querySelector('form');let elements=(form&&form.elements)?[...form.elements]:[];for(let index=0;index<elements.length;index++){let element=elements[index];if(element.type!=='hidden'&&element.type!=='button'&&element.type!=='submit'&&!element.disabled){element.focus();break;}}};let close=function(event){document.documentElement.classList.remove("modal-open");element.classList.add("close");element.classList.remove("open");};if(name){document.querySelectorAll("[data-ui-modal-ref='"+name+"']").forEach(function(elem){elem.addEventListener("click",open);});}
let open=function(){document.documentElement.classList.add("modal-open");document.dispatchEvent(new CustomEvent("modal-open",{bubbles:false,cancelable:true}));element.classList.add("open");element.classList.remove("close");let form=element.querySelector('form');let elements=form?form.querySelectorAll('[name]'):[];for(const element of elements){if(element.type!=='hidden'&&element.type!=='button'&&element.type!=='submit'&&!element.disabled){element.focus();break;}}};let close=function(event){document.documentElement.classList.remove("modal-open");element.classList.add("close");element.classList.remove("open");};if(name){document.querySelectorAll("[data-ui-modal-ref='"+name+"']").forEach(function(elem){elem.addEventListener("click",open);});}
if(openEvent){document.addEventListener(openEvent,open);}
buttonElements.forEach(button=>{button.addEventListener("click",open);});document.addEventListener("keydown",function(event){if(event.which===27){close();}});element.addEventListener("blur",close);let closeButtons=element.querySelectorAll("[data-ui-modal-close]");for(let i=0;i<closeButtons.length;i++){closeButtons[i].addEventListener("click",close);}
document.addEventListener("modal-close",close);element.addEventListener(closeEvent,close);}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-ls-ui-open",controller:function(element,window){let def=element.classList.contains("open")?"open":"close";let buttonClass=element.dataset["buttonClass"]||"ls-ui-open";let buttonText=element.dataset["buttonText"]||"";let buttonIcon=element.dataset["buttonIcon"]||"";let buttonAria=element.dataset["buttonAria"]||"Open";let buttonSelector=element.dataset["buttonSelector"]||"";let hover=element.hasAttribute("data-hover");let blur=element.hasAttribute("data-blur");let button=window.document.createElement("button");let isTouch=function(){return("ontouchstart"in window||navigator.maxTouchPoints);};button.innerText=buttonText;button.className=buttonClass;button.type="button";if(buttonIcon){let icon=window.document.createElement("i");icon.className=buttonIcon;button.insertBefore(icon,button.firstChild);}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -124,7 +124,13 @@ window.addEventListener("load", async () => {
});
window.formValidation = (form, fields) => {
const elements = form.elements;
const elements = Array.from(form.querySelectorAll('[name]')).reduce((prev, curr) => {
if(!curr.name) {
return prev;
}
prev[curr.name] = curr;
return prev;
}, {});
const actionHandler = (action, attribute) => {
switch (action) {
case "disable":
@ -178,7 +184,7 @@ window.formValidation = (form, fields) => {
form.addEventListener("reset", () => {
for (const key in fields) {
if (Object.hasOwnProperty.call(fields, key)) {
const element = form.elements[key];
const element = elements[key];
element.setAttribute("value", "");
element.removeAttribute("disabled");
element.dispatchEvent(new Event("change"));

View file

@ -85,11 +85,9 @@
element.classList.remove("close");
let form = element.querySelector('form');
let elements = (form && form.elements) ? [...form.elements] : [];
for (let index = 0; index < elements.length; index++) {
let element = elements[index];
let elements = form ? form.querySelectorAll('[name]') : [];
for (const element of elements) {
if(element.type !== 'hidden'
&& element.type !== 'button'
&& element.type !== 'submit'

View file

@ -821,6 +821,7 @@ label.switch {
background: var(--config-color-fade);
display: inline-block;
margin: 0;
margin-bottom: 32px;
padding: 5px;
.func-padding-start(5px);
.func-padding-end(30px);

View file

@ -1130,8 +1130,8 @@ trait DatabaseBase
'releaseYear' => 2017,
'actors' => [],
],
'read' => ['user:'.$this->getUser()['$id'], 'user:testx'],
'write' => ['user:'.$this->getUser()['$id'], 'user:testy'],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$id = $document['body']['$id'];
@ -1139,8 +1139,8 @@ trait DatabaseBase
$this->assertEquals($document['headers']['status-code'], 201);
$this->assertEquals($document['body']['title'], 'Thor: Ragnaroc');
$this->assertEquals($document['body']['releaseYear'], 2017);
$this->assertEquals($document['body']['$read'][1], 'user:testx');
$this->assertEquals($document['body']['$write'][1], 'user:testy');
$this->assertEquals('user:'.$this->getUser()['$id'], $document['body']['$read'][0]);
$this->assertEquals('user:'.$this->getUser()['$id'], $document['body']['$write'][0]);
$document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
'content-type' => 'application/json',
@ -1149,11 +1149,15 @@ trait DatabaseBase
'data' => [
'title' => 'Thor: Ragnarok',
],
'read' => ['role:member'],
'write' => ['role:member'],
]);
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['title'], 'Thor: Ragnarok');
$this->assertEquals($document['body']['releaseYear'], 2017);
$this->assertEquals('role:member', $document['body']['$read'][0]);
$this->assertEquals('role:member', $document['body']['$write'][0]);
$document = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
'content-type' => 'application/json',

View file

@ -1377,22 +1377,9 @@ class ProjectsConsoleClientTest extends Scope
'store' => '',
'hostname' => 'localhost',
]);
$this->assertEquals(400, $response['headers']['status-code']);
// $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'type' => 'web',
// 'name' => 'Web App',
// 'key' => '',
// 'store' => '',
// 'hostname' => 'https://localhost',
// ]);
// $this->assertEquals(400, $response['headers']['status-code']);
return $data;
}
@ -1402,7 +1389,9 @@ class ProjectsConsoleClientTest extends Scope
public function testListProjectPlatform($data): array
{
$id = $data['projectId'] ?? '';
sleep(1);
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -1424,7 +1413,7 @@ class ProjectsConsoleClientTest extends Scope
public function testGetProjectPlatform($data): array
{
$id = $data['projectId'] ?? '';
$platformWebId = $data['platformWebId'] ?? '';
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformWebId, array_merge([

View file

@ -20,11 +20,11 @@ class CollectionsTest extends TestCase
public function testDuplicateRules()
{
foreach ($this->collections as $key => $collection) {
if (array_key_exists('rules', $collection)) {
foreach ($collection['rules'] as $check) {
if (array_key_exists('attributes', $collection)) {
foreach ($collection['attributes'] as $check) {
$occurences = 0;
foreach ($collection['rules'] as $rule) {
if ($rule['key'] == $check['key']) {
foreach ($collection['attributes'] as $attribute) {
if ($attribute['$id'] == $check['$id']) {
$occurences++;
}
}