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

Merge branch '0.7.x' into fix-797-clamav-env-vars

This commit is contained in:
Eldad A. Fux 2021-01-02 22:13:00 +02:00 committed by GitHub
commit 1c9db90507
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 781 additions and 239 deletions

1
.env
View file

@ -33,3 +33,4 @@ _APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=1
_APP_FUNCTIONS_MEMORY=128
_APP_FUNCTIONS_MEMORY_SWAP=128
_APP_MAINTENANCE_INTERVAL=86400

View file

@ -19,20 +19,10 @@ before_install:
- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
- sudo service docker start
# cache:
# directories:
# - docker_images
# before_install:
# - docker load -i docker_images/images.tar || true
# before_cache:
# - docker save -o docker_images/images.tar appwrite_appwrite
install:
- docker --version
- docker-compose up -d
- sleep 90
- sleep 10
script:
- docker ps

View file

@ -52,6 +52,7 @@
- Added option to disable mail sending by setting empty SMTP host
- Upgraded installation script ([#490](https://github.com/appwrite/appwrite/issues/490))
- Added new environment variables for ClamAV hostname and port ([#780](https://github.com/appwrite/appwrite/pull/780))
- Added toggle to hide/show secret keys and passwords inside the dashboard (@kodumbeats, [#535](https://github.com/appwrite/appwrite/issues/535))
## Breaking Changes (Read before upgrading!)
- **Deprecated** `first` and `last` query params for documents list route in the database API

View file

@ -98,7 +98,9 @@ ENV _APP_SERVER=swoole \
_APP_FUNCTIONS_MEMORY=128 \
_APP_FUNCTIONS_MEMORY_SWAP=128 \
_APP_SETUP=self-hosted \
_APP_VERSION=$VERSION
_APP_VERSION=$VERSION \
# 1 Day = 86400 s
_APP_MAINTENANCE_INTERVAL=86400
#ENV _APP_SMTP_SECURE ''
#ENV _APP_SMTP_USERNAME ''
#ENV _APP_SMTP_PASSWORD ''
@ -160,6 +162,7 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/schedule && \

View file

@ -9,6 +9,7 @@ use Utopia\CLI\Console;
$cli = new CLI();
include 'tasks/doctor.php';
include 'tasks/maintenance.php';
include 'tasks/install.php';
include 'tasks/migrate.php';
include 'tasks/sdks.php';

View file

@ -6,6 +6,7 @@ return [
'version' => '14.5',
'base' => 'node:14.5-alpine',
'image' => 'appwrite/env-node-14.5:1.0.0',
'build' => '/usr/src/code/docker/environments/node-14.5',
'logo' => 'node.png',
],
'php-7.4' => [
@ -13,6 +14,7 @@ return [
'version' => '7.4',
'base' => 'php:7.4-cli-alpine',
'image' => 'appwrite/env-php-7.4:1.0.0',
'build' => '/usr/src/code/docker/environments/php-7.4',
'logo' => 'php.png',
],
'php-8.0' => [
@ -20,6 +22,7 @@ return [
'version' => '8.0',
'base' => 'php:8.0-cli-alpine',
'image' => 'appwrite/env-php-8.0:1.0.0',
'build' => '/usr/src/code/docker/environments/php-8.0',
'logo' => 'php.png',
],
'ruby-2.7' => [
@ -27,6 +30,7 @@ return [
'version' => '2.7',
'base' => 'ruby:2.7-alpine',
'image' => 'appwrite/env-ruby-2.7:1.0.2',
'build' => '/usr/src/code/docker/environments/ruby-2.7',
'logo' => 'ruby.png',
],
'python-3.8' => [
@ -34,6 +38,7 @@ return [
'version' => '3.8',
'base' => 'python:3.8-alpine',
'image' => 'appwrite/env-python-3.8:1.0.0',
'build' => '/usr/src/code/docker/environments/python-3.8',
'logo' => 'python.png',
],
'deno-1.2' => [
@ -41,6 +46,7 @@ return [
'version' => '1.2',
'base' => 'hayd/deno:alpine-1.2.0',
'image' => 'appwrite/env-deno-1.2:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.2',
'logo' => 'deno.png',
],
'deno-1.5' => [
@ -48,6 +54,7 @@ return [
'version' => '1.5',
'base' => 'hayd/deno:alpine-1.5.0',
'image' => 'appwrite/env-deno-1.5:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.5',
'logo' => 'deno.png',
],
// 'dart-2.8' => [
@ -55,6 +62,7 @@ return [
// 'version' => '2.8',
// 'base' => 'google/dart:2.8',
// 'image' => 'appwrite/env-dart:2.8',
// 'build' => '/usr/src/code/docker/environments/dart-2.8',
// 'logo' => 'dart.png',
// ],
];

View file

@ -24,6 +24,8 @@ $logged = [
'projects.write',
'locale.read',
'avatars.read',
'execution.read',
'execution.write',
];
$admins = [
@ -37,8 +39,6 @@ $admins = [
'users.write',
'collections.read',
'collections.write',
'functions.read',
'functions.write',
'platforms.read',
'platforms.write',
'keys.read',
@ -50,6 +50,10 @@ $admins = [
'locale.read',
'avatars.read',
'health.read',
'functions.read',
'functions.write',
'execution.read',
'execution.write',
];
return [
@ -63,6 +67,8 @@ return [
'files.read',
'locale.read',
'avatars.read',
'execution.read',
'execution.write',
],
],
ROLE_MEMBER => [

View file

@ -1,28 +1,55 @@
<?php
return [ // List of publicly visible scopes
'users.read',
'users.write',
'teams.read',
'teams.write',
'collections.read',
'collections.write',
'documents.read',
'documents.write',
'files.read',
'files.write',
'functions.read',
'functions.write',
'health.read',
// 'platforms.read',
// 'platforms.write',
// 'keys.read',
// 'keys.write',
// 'tasks.read',
// 'tasks.write',
// 'webhooks.read',
// 'webhooks.write',
'locale.read',
'avatars.read',
'health.read',
'users.read' => [
'description' => 'Access to read your project\'s users',
],
'users.write' => [
'description' => 'Access to create, update, and delete your project\'s users',
],
'teams.read' => [
'description' => 'Access to read your project\'s teams',
],
'teams.write' => [
'description' => 'Access to create, update, and delete your project\'s teams',
],
'collections.read' => [
'description' => 'Access to read your project\'s database collections',
],
'collections.write' => [
'description' => 'Access to create, update, and delete your project\'s database collections',
],
'documents.read' => [
'description' => 'Access to read your project\'s database documents',
],
'documents.write' => [
'description' => 'Access to create, update, and delete your project\'s database documents',
],
'files.read' => [
'description' => 'Access to read your project\'s storage files and preview images',
],
'files.write' => [
'description' => 'Access to create, update, and delete your project\'s storage files',
],
'functions.read' => [
'description' => 'Access to read your project\'s functions and code tags',
],
'functions.write' => [
'description' => 'Access to create, update, and delete your project\'s functions and code tags',
],
'execution.read' => [
'description' => 'Access to read your project\'s execution logs',
],
'execution.write' => [
'description' => 'Access to execute your project\'s functions',
],
'locale.read' => [
'description' => 'Access to access your project\'s Locale service',
],
'avatars.read' => [
'description' => 'Access to access your project\'s Avatars service',
],
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
];;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -187,4 +187,10 @@ return [
'required' => false,
'question' => '',
],
[
'name' => '_APP_MAINTENANCE_INTERVAL',
'default' => '86400',
'required' => false,
'question' => '',
],
];

View file

@ -265,6 +265,7 @@ App::delete('/v1/database/collections/:collectionId')
}
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
->setParam('document', $collection)
;

View file

@ -2,6 +2,7 @@
use Appwrite\Database\Database;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\Authorization;
use Appwrite\Database\Validator\UID;
use Appwrite\Storage\Storage;
use Appwrite\Storage\Validator\File;
@ -18,6 +19,7 @@ use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Utopia\Config\Config;
use Cron\CronExpression;
use Utopia\Exception;
include_once __DIR__ . '/../shared/api.php';
@ -33,6 +35,7 @@ App::post('/v1/functions')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('env', '', new WhiteList(array_keys(Config::getParam('environments')), true), 'Execution enviornment.')
->param('vars', [], new Assoc(), 'Key-value JSON object.', true)
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
@ -40,12 +43,11 @@ App::post('/v1/functions')
->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
->inject('response')
->inject('projectDB')
->action(function ($name, $env, $vars, $events, $schedule, $timeout, $response, $projectDB) {
->action(function ($name, $execute, $env, $vars, $events, $schedule, $timeout, $response, $projectDB) {
$function = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS,
'$permissions' => [
'read' => [],
'write' => [],
'execute' => $execute,
],
'dateCreated' => time(),
'dateUpdated' => time(),
@ -259,13 +261,14 @@ App::put('/v1/functions/:functionId')
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function unique ID.')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('vars', [], new Assoc(), 'Key-value JSON object.', true)
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
->inject('response')
->inject('projectDB')
->action(function ($functionId, $name, $vars, $events, $schedule, $timeout, $response, $projectDB) {
->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $projectDB) {
$function = $projectDB->getDocument($functionId);
if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
@ -276,6 +279,9 @@ App::put('/v1/functions/:functionId')
$next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null;
$function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
'$permissions' => [
'execute' => $execute,
],
'dateUpdated' => time(),
'name' => $name,
'vars' => $vars,
@ -313,7 +319,7 @@ App::patch('/v1/functions/:functionId/tag')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateTag')
->label('sdk.description', '/docs/references/functions/update-tag.md')
->label('sdk.description', '/docs/references/functions/update-function-tag.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
@ -380,6 +386,7 @@ App::delete('/v1/functions/:functionId')
}
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
->setParam('document', $function->getArrayCopy())
;
@ -614,8 +621,8 @@ App::delete('/v1/functions/:functionId/tags/:tagId')
App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('Create Execution')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('scope', 'execution.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createExecution')
->label('sdk.description', '/docs/references/functions/create-execution.md')
@ -632,6 +639,8 @@ App::post('/v1/functions/:functionId/executions')
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Database $projectDB */
Authorization::disable();
$function = $projectDB->getDocument($functionId);
if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
@ -647,11 +656,21 @@ App::post('/v1/functions/:functionId/executions')
if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) {
throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
}
Authorization::reset();
$validator = new Authorization($function, 'execute');
if (!$validator->isValid($function->getPermissions())) { // Check if user has write access to execute function
throw new Exception($validator->getDescription(), 401);
}
Authorization::disable();
$execution = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_EXECUTIONS,
'$permissions' => [
'read' => [],
'read' => $function->getPermissions()['execute'] ?? [],
'write' => [],
],
'dateCreated' => time(),
@ -664,6 +683,8 @@ App::post('/v1/functions/:functionId/executions')
'time' => 0,
]);
Authorization::reset();
if (false === $execution) {
throw new Exception('Failed saving execution to DB', 500);
}
@ -684,8 +705,8 @@ App::post('/v1/functions/:functionId/executions')
App::get('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('List Executions')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('scope', 'execution.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listExecutions')
->label('sdk.description', '/docs/references/functions/list-executions.md')
@ -726,8 +747,8 @@ App::get('/v1/functions/:functionId/executions')
App::get('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Get Execution')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('scope', 'execution.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getExecution')
->label('sdk.description', '/docs/references/functions/get-execution.md')

View file

@ -5,6 +5,7 @@ use Utopia\Exception;
use Appwrite\Storage\Device\Local;
use Appwrite\Storage\Storage;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
App::get('/v1/health')
->desc('Get HTTP')
@ -130,8 +131,8 @@ App::get('/v1/health/queue/webhooks')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-webhooks')]);
});
$response->json(['size' => Resque::size(Event::WEBHOOK_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/queue/tasks')
->desc('Get Tasks Queue')
@ -145,8 +146,8 @@ App::get('/v1/health/queue/tasks')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-tasks')]);
});
$response->json(['size' => Resque::size(Event::TASK_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/queue/logs')
->desc('Get Logs Queue')
@ -160,8 +161,8 @@ App::get('/v1/health/queue/logs')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-audit')]);
});
$response->json(['size' => Resque::size(Event::AUDITS_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/queue/usage')
->desc('Get Usage Queue')
@ -175,8 +176,8 @@ App::get('/v1/health/queue/usage')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-usage')]);
});
$response->json(['size' => Resque::size(Event::USAGE_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/queue/certificates')
->desc('Get Certificate Queue')
@ -190,8 +191,8 @@ App::get('/v1/health/queue/certificates')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-certificates')]);
});
$response->json(['size' => Resque::size(Event::CERTIFICATES_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/queue/functions')
->desc('Get Functions Queue')
@ -205,8 +206,8 @@ App::get('/v1/health/queue/functions')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->json(['size' => Resque::size('v1-functions')]);
});
$response->json(['size' => Resque::size(Event::FUNCTIONS_QUEUE_NAME)]);
}, ['response']);
App::get('/v1/health/storage/local')
->desc('Get Local Storage')

View file

@ -464,7 +464,10 @@ App::delete('/v1/projects/:projectId')
throw new Exception('Project not found', 404);
}
$deletes->setParam('document', $project->getArrayCopy());
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
->setParam('document', $project->getArrayCopy())
;
foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms)
$list = $project->getAttribute('webhooks', []);
@ -715,7 +718,7 @@ App::post('/v1/projects/:projectId/keys')
->label('sdk.response.model', Response::MODEL_KEY)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(Config::getParam('scopes'), true)), 'Key scopes list.')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list.')
->inject('response')
->inject('consoleDB')
->action(function ($projectId, $name, $scopes, $response, $consoleDB) {
@ -828,7 +831,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('projectId', null, new UID(), 'Project unique ID.')
->param('keyId', null, new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(Config::getParam('scopes'), true)), 'Key scopes list')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list')
->inject('response')
->inject('consoleDB')
->action(function ($projectId, $keyId, $name, $scopes, $response, $consoleDB) {

View file

@ -545,6 +545,7 @@ App::delete('/v1/users/:userId')
}
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
->setParam('document', $user)
;

View file

@ -294,7 +294,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
$audits->trigger();
}
if (!empty($deletes->getParam('document'))) {
if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
$deletes->trigger();
}

View file

@ -177,7 +177,7 @@ App::get('/console/keys')
->action(function ($layout) {
/** @var Utopia\View $layout */
$scopes = Config::getParam('scopes');
$scopes = array_keys(Config::getParam('scopes'));
$page = new View(__DIR__.'/../../views/console/keys/index.phtml');
$page->setParam('scopes', $scopes);

View file

@ -52,7 +52,12 @@ const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
// Deletion Types
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
$register = new Registry();
@ -298,19 +303,19 @@ App::setResource('events', function($register) {
}, ['register']);
App::setResource('audits', function($register) {
return new Event('v1-audits', 'AuditsV1');
return new Event(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME);
}, ['register']);
App::setResource('usage', function($register) {
return new Event('v1-usage', 'UsageV1');
return new Event(Event::USAGE_QUEUE_NAME, Event::USAGE_CLASS_NAME);
}, ['register']);
App::setResource('mails', function($register) {
return new Event('v1-mails', 'MailsV1');
return new Event(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME);
}, ['register']);
App::setResource('deletes', function($register) {
return new Event('v1-deletes', 'DeletesV1');
return new Event(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME);
}, ['register']);
// Test Mock

67
app/tasks/maintenance.php Normal file
View file

@ -0,0 +1,67 @@
<?php
global $cli;
require_once __DIR__.'/../init.php';
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Event\Event;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
// TODO: Think of a better way to access consoleDB
function getConsoleDB() {
global $register;
$consoleDB = new Database();
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
$consoleDB->setNamespace('app_console'); // Main DB
$consoleDB->setMocks(Config::getParam('collections', []));
return $consoleDB;
}
function notifyDeleteExecutionLogs()
{
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
'type' => DELETE_TYPE_EXECUTIONS
]);
}
function notifyDeleteAbuseLogs(int $interval)
{
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
'type' => DELETE_TYPE_ABUSE,
'timestamp' => time() - $interval
]);
}
function notifyDeleteAuditLogs(int $interval)
{
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
'type' => DELETE_TYPE_AUDIT,
'timestamp' => time() - $interval
]);
}
$cli
->task('maintenance')
->desc('Schedules maintenance tasks and publishes them to resque')
->action(function () {
// # of days in seconds (1 day = 86400s)
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
//Convert Seconds to microseconds
$intervalMicroseconds = $interval * 1000000;
$consoleDB = getConsoleDB();
Console::loop(function() use ($consoleDB, $interval){
Console::info("[ MAINTENANCE TASK ] Notifying deletes workers every {$interval} seconds");
notifyDeleteExecutionLogs();
notifyDeleteAbuseLogs($interval);
notifyDeleteAuditLogs($interval);
}, $intervalMicroseconds);
});

View file

@ -506,6 +506,10 @@ $timeout = $this->getParam('timeout', 900);
</div>
</section>
<label for="execute">Execute Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
<input type="hidden" id="execute" name="execute" data-forms-tags data-cast-to="json" data-ls-bind="{{project-function.$permissions.execute}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<section class="margin-bottom-large">
<label for="schedule">Schedule (CRON Syntax) <span class="tooltip small" data-tooltip="Set a CRON schedule to trigger this function."><i class="icon-info-circled"></i></span></label>
<input type="text" class="full-width" name="schedule" autocomplete="off" data-ls-bind="{{project-function.schedule}}" placeholder="* * * * *" />

View file

@ -189,7 +189,7 @@
</div>
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpPass-{{task.$id}}">Password</label>
<input type="password" class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{task.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{task.httpPass}}" />
<input data-forms-show-secret type="password" class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{task.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{task.httpPass}}" />
</div>
</div>
</div>
@ -335,7 +335,7 @@
</div>
<div class="col span-6 margin-bottom">
<label for="httpPass">Password</label>
<input type="password" class="full-width margin-bottom-no" id="httpPass" name="httpPass" autocomplete="off" />
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" id="httpPass" name="httpPass" autocomplete="off" />
</div>
</div>
</div>

View file

@ -339,7 +339,7 @@ $providers = $this->getParam('providers', []);
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}}">
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
<input name="secret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<input name="secret" data-forms-show-secret data-forms-show-secret-above="true" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="password" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<?php else: ?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Bundle ID <span class="tooltip" data-tooltip="Attribute internal display name"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="com.company.appname" />

View file

@ -104,7 +104,7 @@ $events = array_keys($this->getParam('events', []));
</div>
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpPass-{{webhook.$id}}">Password</label>
<input type="password" class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{webhook.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{webhook.httpPass}}" />
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{webhook.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{webhook.httpPass}}" />
</div>
</div>
</div>
@ -220,7 +220,7 @@ $events = array_keys($this->getParam('events', []));
</div>
<div class="col span-6 margin-bottom">
<label for="httpPass">Password</label>
<input type="password" class="full-width margin-bottom-no" id="httpPass" name="httpPass" autocomplete="off" />
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" id="httpPass" name="httpPass" autocomplete="off" />
</div>
</div>
</div>

View file

@ -261,6 +261,27 @@ services:
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
appwrite-maintenance:
entrypoint: maintenance
container_name: appwrite-maintenance
build:
context: .
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_MAINTENANCE_INTERVAL
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-schedule:
image: appwrite/appwrite:<?php echo $version."\n"; ?>

View file

@ -1,22 +1,27 @@
<?php
require_once __DIR__.'/../init.php';
\cli_set_process_title('Deletes V1 Worker');
echo APP_NAME.' deletes worker v1 has started'."\n";
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\Authorization;
use Appwrite\Storage\Device\Local;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Audit\Audit;
use Utopia\Audit\Adapters\MySQL as AuditAdapter;
require_once __DIR__.'/../init.php';
\cli_set_process_title('Deletes V1 Worker');
Console::success(APP_NAME.' deletes worker v1 has started'."\n");
class DeletesV1
{
public $args = [];
protected $consoleDB = null;
@ -27,28 +32,49 @@ class DeletesV1
public function perform()
{
$projectId = $this->args['projectId'];
$document = $this->args['document'];
$document = new Document($document);
$projectId = $this->args['projectId'];
$type = $this->args['type'];
switch (strval($document->getCollection())) {
case Database::SYSTEM_COLLECTION_PROJECTS:
$this->deleteProject($document);
switch (strval($type)) {
case DELETE_TYPE_DOCUMENT:
$document = $this->args['document'];
$document = new Document($document);
switch (strval($document->getCollection())) {
case Database::SYSTEM_COLLECTION_PROJECTS:
$this->deleteProject($document);
break;
case Database::SYSTEM_COLLECTION_FUNCTIONS:
$this->deleteFunction($document, $projectId);
break;
case Database::SYSTEM_COLLECTION_USERS:
$this->deleteUser($document, $projectId);
break;
case Database::SYSTEM_COLLECTION_COLLECTIONS:
$this->deleteDocuments($document, $projectId);
break;
default:
Console::error('No lazy delete operation available for document of type: '.$document->getCollection());
break;
}
break;
case Database::SYSTEM_COLLECTION_FUNCTIONS:
$this->deleteFunction($document, $projectId);
case DELETE_TYPE_EXECUTIONS:
$this->deleteExecutionLogs();
break;
case Database::SYSTEM_COLLECTION_USERS:
$this->deleteUser($document, $projectId);
case DELETE_TYPE_AUDIT:
$this->deleteAuditLogs($this->args['timestamp']);
break;
case Database::SYSTEM_COLLECTION_COLLECTIONS:
$this->deleteDocuments($document, $projectId);
case DELETE_TYPE_ABUSE:
$this->deleteAbuseLogs($this->args['timestamp']);
break;
default:
Console::error('No lazy delete operation available for document of type: '.$document->getCollection());
Console::error('No delete operation for type: '.$type);
break;
}
}
}
public function tearDown(): void
@ -84,7 +110,7 @@ class DeletesV1
foreach ($tokens as $token) {
if (!$this->getProjectDB($projectId)->deleteDocument($token->getId())) {
throw new Exception('Failed to remove token from DB', 500);
throw new Exception('Failed to remove token from DB');
}
}
@ -95,6 +121,59 @@ class DeletesV1
], $this->getProjectDB($projectId));
}
protected function deleteExecutionLogs()
{
$this->deleteForProjectIds(function($projectId) {
if (!($projectDB = $this->getProjectDB($projectId))) {
throw new Exception('Failed to get projectDB for project '.$projectId);
}
// Delete Executions
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS
], $projectDB);
});
}
protected function deleteAbuseLogs($timestamp)
{
global $register;
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
}
$timeLimit = new TimeLimit("", 0, 1, function () use ($register) {
return $register->get('db');
});
$this->deleteForProjectIds(function($projectId) use ($timeLimit, $timestamp){
$timeLimit->setNamespace('app_'.$projectId);
$abuse = new Abuse($timeLimit);
$status = $abuse->cleanup($timestamp);
if (!$status) {
throw new Exception('Failed to delete Abuse logs for project '.$projectId);
}
});
}
protected function deleteAuditLogs($timestamp)
{
global $register;
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
}
$this->deleteForProjectIds(function($projectId) use ($register, $timestamp){
$adapter = new AuditAdapter($register->get('db'));
$adapter->setNamespace('app_'.$projectId);
$audit = new Audit($adapter);
$status = $audit->cleanup($timestamp);
if (!$status) {
throw new Exception('Failed to delete Audit logs for project'.$projectId);
}
});
}
protected function deleteFunction(Document $document, $projectId)
{
$projectDB = $this->getProjectDB($projectId);
@ -142,6 +221,48 @@ class DeletesV1
Authorization::reset();
}
protected function deleteForProjectIds(callable $callback)
{
$count = 0;
$chunk = 0;
$limit = 50;
$projects = [];
$sum = $limit;
$executionStart = \microtime(true);
while($sum === $limit) {
$chunk++;
Authorization::disable();
$projects = $this->getConsoleDB()->getCollection([
'limit' => $limit,
'offset' => $count,
'orderType' => 'ASC',
'orderCast' => 'string',
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_PROJECTS,
],
]);
Authorization::reset();
$projectIds = array_map (function ($project) {
return $project->getId();
}, $projects);
$sum = count($projects);
Console::info('Executing delete function for chunk #'.$chunk.'. Found '.$sum.' projects');
foreach ($projectIds as $projectId) {
$callback($projectId);
$count++;
}
}
$executionEnd = \microtime(true);
Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds");
}
protected function deleteByGroup(array $filters, Database $database, callable $callback = null)
{
$count = 0;
@ -159,7 +280,10 @@ class DeletesV1
$results = $database->getCollection([
'limit' => $limit,
'offset' => 0,
'offset' => $count,
'orderField' => '$id',
'orderType' => 'ASC',
'orderCast' => 'string',
'filters' => $filters,
]);

View file

@ -39,7 +39,12 @@ Co\run(function() use ($environments) { // Warmup: make sure images are ready t
Console::info('Warming up '.$environment['name'].' environment...');
Console::execute('docker pull '.$environment['image'], '', $stdout, $stderr);
if(App::isDevelopment()) {
Console::execute('docker build '.$environment['build'].' -t '.$environment['image'], '', $stdout, $stderr);
}
else {
Console::execute('docker pull '.$environment['image'], '', $stdout, $stderr);
}
if(!empty($stdout)) {
Console::log($stdout);
@ -109,8 +114,6 @@ $stdout = \explode("\n", $stdout);
}
}, $stdout);
var_dump(json_encode($list));
Console::info(count($list)." functions listed in " . ($executionEnd - $executionStart) . " seconds with exit code {$exitCode}");
/*
@ -283,7 +286,7 @@ class FunctionsV1
*/
public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $payload = ''): void
{
global $register, $list;
global $list;
$environments = Config::getParam('environments');
@ -489,29 +492,21 @@ class FunctionsV1
if(\count($list) > $max) {
Console::info('Starting containers cleanup');
$sorted = [];
foreach($list as $env) {
$sorted[] = [
'name' => $env['name'],
'created' => (int)($env['appwrite-created'] ?? 0)
];
}
\usort($sorted, function ($item1, $item2) {
return $item1['created'] <=> $item2['created'];
\usort($list, function ($item1, $item2) {
return (int)($item1['appwrite-created'] ?? 0) <=> (int)($item2['appwrite-created'] ?? 0);
});
while(\count($sorted) > $max) {
$first = \array_shift($sorted);
while(\count($list) > $max) {
$first = \array_shift($list);
$stdout = '';
$stderr = '';
if(Console::execute("docker stop {$first['name']}", '', $stdout, $stderr, 30) !== 0) {
if(Console::execute("docker rm -f {$first['name']}", '', $stdout, $stderr, 30) !== 0) {
Console::error('Failed to remove container: '.$stderr);
}
Console::info('Removed container: '.$first['name']);
else {
Console::info('Removed container: '.$first['name']);
}
}
}
}

View file

@ -7,7 +7,7 @@ require_once __DIR__.'/../init.php';
\cli_set_process_title('Mails V1 Worker');
echo APP_NAME.' mails worker v1 has started'."\n";
Console::success(APP_NAME.' mails worker v1 has started'."\n");
class MailsV1
{

3
bin/maintenance Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php maintenance $@

View file

@ -35,10 +35,10 @@
"appwrite/php-clamav": "1.0.*",
"utopia-php/framework": "0.10.0",
"utopia-php/abuse": "0.2.*",
"utopia-php/audit": "0.3.*",
"utopia-php/abuse": "0.3.*",
"utopia-php/audit": "0.5.*",
"utopia-php/cache": "0.2.*",
"utopia-php/cli": "0.7.3",
"utopia-php/cli": "0.8.0",
"utopia-php/config": "0.2.*",
"utopia-php/locale": "0.3.*",
"utopia-php/registry": "0.2.*",

25
composer.lock generated
View file

@ -1112,16 +1112,16 @@
},
{
"name": "utopia-php/abuse",
"version": "0.2.2",
"version": "0.3.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "8e65890a6d7afa9f57992f1eca9fe64508f9822e"
"reference": "23c2eb533bca8f3ef5548ae265398fa7d4d39a1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/8e65890a6d7afa9f57992f1eca9fe64508f9822e",
"reference": "8e65890a6d7afa9f57992f1eca9fe64508f9822e",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/23c2eb533bca8f3ef5548ae265398fa7d4d39a1c",
"reference": "23c2eb533bca8f3ef5548ae265398fa7d4d39a1c",
"shasum": ""
},
"require": {
@ -1164,16 +1164,16 @@
},
{
"name": "utopia-php/audit",
"version": "0.3.2",
"version": "0.5.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "544ecff78788d11f60992a721f102cafc22ab084"
"reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/544ecff78788d11f60992a721f102cafc22ab084",
"reference": "544ecff78788d11f60992a721f102cafc22ab084",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/154a850170a58667a15e4b65fbabb6cd0b709dd9",
"reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9",
"shasum": ""
},
"require": {
@ -1268,16 +1268,16 @@
},
{
"name": "utopia-php/cli",
"version": "0.7.3",
"version": "0.8",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
"reference": "0337918242278e0cf98f8dcab2e75b5a3153b856"
"reference": "090c7ae22b53a175d962e8f9601d36350496153c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/0337918242278e0cf98f8dcab2e75b5a3153b856",
"reference": "0337918242278e0cf98f8dcab2e75b5a3153b856",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/090c7ae22b53a175d962e8f9601d36350496153c",
"reference": "090c7ae22b53a175d962e8f9601d36350496153c",
"shasum": ""
},
"require": {
@ -1875,6 +1875,7 @@
"require-dev": {
"phpunit/phpunit": "^7.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {

View file

@ -55,6 +55,7 @@ services:
- traefik.http.routers.appwrite-secure.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite-secure.tls=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
@ -271,6 +272,7 @@ services:
- appwrite-functions:/storage/functions:rw
- /tmp:/tmp:rw
- ./app:/usr/src/code/app
- ./docker:/usr/src/code/docker
- ./src:/usr/src/code/src
depends_on:
- redis
@ -317,6 +319,27 @@ services:
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
appwrite-maintenance:
entrypoint: maintenance
container_name: appwrite-maintenance
build:
context: .
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_MAINTENANCE_INTERVAL
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-schedule:
entrypoint: schedule
container_name: appwrite-schedule

View file

@ -0,0 +1,14 @@
let sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.createExecution('[FUNCTION_ID]');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -0,0 +1,14 @@
let sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.getExecution('[FUNCTION_ID]', '[EXECUTION_ID]');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -0,0 +1,14 @@
let sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.listExecutions('[FUNCTION_ID]');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -6,7 +6,7 @@ sdk
.setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;
let promise = sdk.functions.create('[NAME]', 'node-14');
let promise = sdk.functions.create('[NAME]', [], 'node-14');
promise.then(function (response) {
console.log(response); // Success

View file

@ -6,7 +6,7 @@ sdk
.setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;
let promise = sdk.functions.update('[FUNCTION_ID]', '[NAME]');
let promise = sdk.functions.update('[FUNCTION_ID]', '[NAME]', []);
promise.then(function (response) {
console.log(response); // Success

View file

@ -1 +1 @@
Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.
The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.

View file

@ -1 +1 @@
Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project collections. [Learn more about different API modes](/docs/admin).
Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's collections. [Learn more about different API modes](/docs/admin).

View file

@ -1 +1 @@
Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](/docs/admin).
Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's documents. [Learn more about different API modes](/docs/admin).

View file

@ -0,0 +1 @@
Trigger a function execution. The returned object will return you the current execution status. You can ping the `Get Execution` endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously.

View file

@ -0,0 +1 @@
Create a new function. You can pass a list of [permissions](/docs/permissions) to allow different project users or team with access to execute the function using the client API.

View file

@ -0,0 +1,5 @@
Create a new function code tag. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's tag to use your new tag UID.
This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](/docs/functions).
Use the "command" param to set the entry point used to execute your code.

View file

@ -0,0 +1 @@
Delete a function by its unique ID.

View file

@ -0,0 +1 @@
Delete a code tag by its unique ID.

View file

@ -0,0 +1 @@
Get a function execution log by its unique ID.

View file

@ -0,0 +1 @@
Get a function by its unique ID.

View file

@ -0,0 +1 @@
Get a code tag by its unique ID.

View file

@ -0,0 +1 @@
Get a list of all the current user function execution logs. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's teams. [Learn more about different API modes](/docs/admin).

View file

@ -0,0 +1 @@
Get a list of all the project's functions. You can use the query params to filter your results.

View file

@ -0,0 +1 @@
Get a list of all the project's code tags. You can use the query params to filter your results.

View file

@ -0,0 +1 @@
Update the function code tag ID using the unique function ID. Use this endpoint to switch the code tag that should be executed by the execution endpoint.

View file

@ -0,0 +1 @@
Update function by its unique ID.

View file

@ -1 +1 @@
Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
Get a file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.

View file

@ -1 +1 @@
Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.
Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.

View file

@ -1 +1 @@
Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.
Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata.

View file

@ -1 +1 @@
Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](/docs/admin).
Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's files. [Learn more about different API modes](/docs/admin).

View file

@ -1 +1 @@
Update file by its unique ID. Only users with write permissions have access to update this resource.
Update a file by its unique ID. Only users with write permissions have access to update this resource.

View file

@ -1 +1 @@
Delete team by its unique ID. Only team owners have write access for this resource.
Delete a team by its unique ID. Only team owners have write access for this resource.

View file

@ -1 +1 @@
Get team members by the team unique ID. All team members have read access for this list of resources.
Get a team members by the team unique ID. All team members have read access for this list of resources.

View file

@ -1 +1 @@
Get team by its unique ID. All team members have read access for this resource.
Get a team by its unique ID. All team members have read access for this resource.

View file

@ -1 +1 @@
Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](/docs/admin).
Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's teams. [Learn more about different API modes](/docs/admin).

View file

@ -1 +1 @@
Update team by its unique ID. Only team owners have write access for this resource.
Update a team by its unique ID. Only team owners have write access for this resource.

View file

@ -1 +1 @@
Delete user sessions by its unique ID.
Delete a user sessions by its unique ID.

View file

@ -1 +1 @@
Delete all user sessions by its unique ID.
Delete all user's sessions by using the user's unique ID.

View file

@ -1 +1 @@
Get user activity logs list by its unique ID.
Get a user activity logs list by its unique ID.

View file

@ -1 +1 @@
Get user preferences by its unique ID.
Get the user preferences by its unique ID.

View file

@ -1 +1 @@
Get user sessions list by its unique ID.
Get the user sessions list by its unique ID.

View file

@ -1 +1 @@
Get user by its unique ID.
Get a user by its unique ID.

View file

@ -1 +1 @@
Get a list of all the project users. You can use the query params to filter your results.
Get a list of all the project's users. You can use the query params to filter your results.

View file

@ -1 +1 @@
Update user preferences by its unique ID. You can pass only the specific settings you wish to update.
Update the user preferences by its unique ID. You can pass only the specific settings you wish to update.

View file

@ -1 +1 @@
Update user status by its unique ID.
Update the user status by its unique ID.

View file

@ -56,6 +56,7 @@ const configApp = {
'public/scripts/views/forms/remove.js',
'public/scripts/views/forms/run.js',
'public/scripts/views/forms/select-all.js',
'public/scripts/views/forms/show-secret.js',
'public/scripts/views/forms/switch.js',
'public/scripts/views/forms/tags.js',
'public/scripts/views/forms/text-count.js',

17
package-lock.json generated
View file

@ -3455,7 +3455,8 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
"dev": true
"dev": true,
"optional": true
},
"gulp": {
"version": "4.0.2",
@ -4273,6 +4274,7 @@
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"optional": true,
"requires": {
"is-docker": "^2.0.0"
}
@ -6006,6 +6008,7 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
@ -6322,6 +6325,7 @@
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz",
"integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==",
"dev": true,
"optional": true,
"requires": {
"growly": "^1.3.0",
"is-wsl": "^2.2.0",
@ -6336,6 +6340,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
@ -6344,13 +6349,15 @@
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
"dev": true,
"optional": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"optional": true,
"requires": {
"isexe": "^2.0.0"
}
@ -7317,7 +7324,8 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
@ -8454,7 +8462,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"dev": true,
"optional": true
},
"yargs": {
"version": "7.1.0",

View file

@ -5,7 +5,7 @@
{http.addGlobalHeader('X-Appwrite-Locale',value);config.locale=value;return this;};let setMode=function(value)
{http.addGlobalHeader('X-Appwrite-Mode',value);config.mode=value;return this;};let http=function(document){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(Array.isArray(params[p])){for(let index=0;index<params[p].length;index++){let param=params[p][index];str.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}
return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key]={key:key.toLowerCase(),value:value.toLowerCase()};};let addGlobalParam=function(key,value){globalParams.push({key:key,value:value});};addGlobalHeader('x-sdk-version','appwrite:javascript:1.0.0');addGlobalHeader('content-type','');let call=function(method,path,headers={},params={},progress=null){let i;path=config.endpoint+path;if(-1===['GET','POST','PUT','DELETE','TRACE','HEAD','OPTIONS','CONNECT','PATCH'].indexOf(method)){throw new Error('var method must contain a valid HTTP method name');}
return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key]={key:key.toLowerCase(),value:value.toLowerCase()};};let addGlobalParam=function(key,value){globalParams.push({key:key,value:value});};addGlobalHeader('x-sdk-version','appwrite:web:1.0.0');addGlobalHeader('content-type','');let call=function(method,path,headers={},params={},progress=null){let i;path=config.endpoint+path;if(-1===['GET','POST','PUT','DELETE','TRACE','HEAD','OPTIONS','CONNECT','PATCH'].indexOf(method)){throw new Error('var method must contain a valid HTTP method name');}
if(typeof path!=='string'){throw new Error('var path must be of type string');}
if(typeof headers!=='object'){throw new Error('var headers must be of type object');}
for(i=0;i<globalParams.length;i++){path=addParam(path,globalParams[i].key,globalParams[i].value);}
@ -128,7 +128,7 @@ if(read){payload['read']=read;}
if(write){payload['write']=write;}
if(rules){payload['rules']=rules;}
return http.put(path,{'content-type':'application/json',},payload);},deleteCollection:function(collectionId){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},listDocuments:function(collectionId,filters=[],limit=25,offset=0,orderField='$id',orderType='ASC',orderCast='string',search=''){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},listDocuments:function(collectionId,filters=[],limit=25,offset=0,orderField='',orderType='ASC',orderCast='string',search=''){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/documents'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};if(filters){payload['filters']=filters;}
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
@ -162,18 +162,22 @@ let path='/database/collections/{collectionId}/documents/{documentId}'.replace(n
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
if(orderType){payload['orderType']=orderType;}
return http.get(path,{'content-type':'application/json',},payload);},create:function(name,env,vars=[],events=[],schedule='',timeout=15){if(name===undefined){throw new Error('Missing required parameter: "name"');}
return http.get(path,{'content-type':'application/json',},payload);},create:function(name,execute,env,vars=[],events=[],schedule='',timeout=15){if(name===undefined){throw new Error('Missing required parameter: "name"');}
if(execute===undefined){throw new Error('Missing required parameter: "execute"');}
if(env===undefined){throw new Error('Missing required parameter: "env"');}
let path='/functions';let payload={};if(name){payload['name']=name;}
if(execute){payload['execute']=execute;}
if(env){payload['env']=env;}
if(vars){payload['vars']=vars;}
if(events){payload['events']=events;}
if(schedule){payload['schedule']=schedule;}
if(timeout){payload['timeout']=timeout;}
return http.post(path,{'content-type':'application/json',},payload);},get:function(functionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},update:function(functionId,name,vars=[],events=[],schedule='',timeout=15){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},update:function(functionId,name,execute,vars=[],events=[],schedule='',timeout=15){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(name===undefined){throw new Error('Missing required parameter: "name"');}
if(execute===undefined){throw new Error('Missing required parameter: "execute"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(name){payload['name']=name;}
if(execute){payload['execute']=execute;}
if(vars){payload['vars']=vars;}
if(events){payload['events']=events;}
if(schedule){payload['schedule']=schedule;}
@ -184,9 +188,8 @@ let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}',
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
if(orderType){payload['orderType']=orderType;}
return http.get(path,{'content-type':'application/json',},payload);},createExecution:function(functionId,async=1){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(async){payload['async']=async;}
return http.post(path,{'content-type':'application/json',},payload);},getExecution:function(functionId,executionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
return http.get(path,{'content-type':'application/json',},payload);},createExecution:function(functionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.post(path,{'content-type':'application/json',},payload);},getExecution:function(functionId,executionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(executionId===undefined){throw new Error('Missing required parameter: "executionId"');}
let path='/functions/{functionId}/executions/{executionId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{executionId}','g'),executionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},updateTag:function(functionId,tag){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(tag===undefined){throw new Error('Missing required parameter: "tag"');}
@ -205,7 +208,7 @@ return http.post(path,{'content-type':'multipart/form-data',},payload);},getTag:
if(tagId===undefined){throw new Error('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},deleteTag:function(functionId,tagId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(tagId===undefined){throw new Error('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(functionId,range='last30'){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(functionId,range='30d'){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/usage'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(range){payload['range']=range;}
return http.get(path,{'content-type':'application/json',},payload);}};let health={get:function(){let path='/health';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getAntiVirus:function(){let path='/health/anti-virus';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCache:function(){let path='/health/cache';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getDB:function(){let path='/health/db';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueCertificates:function(){let path='/health/queue/certificates';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueFunctions:function(){let path='/health/queue/functions';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueLogs:function(){let path='/health/queue/logs';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueTasks:function(){let path='/health/queue/tasks';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueUsage:function(){let path='/health/queue/usage';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueWebhooks:function(){let path='/health/queue/webhooks';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getStorageLocal:function(){let path='/health/storage/local';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getTime:function(){let path='/health/time';let payload={};return http.get(path,{'content-type':'application/json',},payload);}};let locale={get:function(){let path='/locale';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getContinents:function(){let path='/locale/continents';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountries:function(){let path='/locale/countries';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountriesEU:function(){let path='/locale/countries/eu';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountriesPhones:function(){let path='/locale/countries/phones';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCurrencies:function(){let path='/locale/currencies';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getLanguages:function(){let path='/locale/languages';let payload={};return http.get(path,{'content-type':'application/json',},payload);}};let projects={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/projects';let payload={};if(search){payload['search']=search;}
if(limit){payload['limit']=limit;}
@ -329,7 +332,7 @@ if(httpUser){payload['httpUser']=httpUser;}
if(httpPass){payload['httpPass']=httpPass;}
return http.put(path,{'content-type':'application/json',},payload);},deleteTask:function(projectId,taskId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
if(taskId===undefined){throw new Error('Missing required parameter: "taskId"');}
let path='/projects/{projectId}/tasks/{taskId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{taskId}','g'),taskId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(projectId,range='last30'){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/tasks/{taskId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{taskId}','g'),taskId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(projectId,range='30d'){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/usage'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(range){payload['range']=range;}
return http.get(path,{'content-type':'application/json',},payload);},listWebhooks:function(projectId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/webhooks'.replace(new RegExp('{projectId}','g'),projectId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},createWebhook:function(projectId,name,events,url,security,httpUser='',httpPass=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
@ -387,9 +390,8 @@ if(background){payload['background']=background;}
if(output){payload['output']=output;}
payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{query.push(encodeURIComponent(p)+"="+encodeURIComponent(payload[p]));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');},getFileView:function(fileId,as=''){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace(new RegExp('{fileId}','g'),fileId);let payload={};if(as){payload['as']=as;}
payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');},getFileView:function(fileId){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace(new RegExp('{fileId}','g'),fileId);let payload={};payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{query.push(encodeURIComponent(p)+"="+encodeURIComponent(payload[p]));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');}};let teams={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/teams';let payload={};if(search){payload['search']=search;}
if(limit){payload['limit']=limit;}
@ -2329,7 +2331,8 @@ score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){va
if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';}
else{paragraph.style.direction='ltr';paragraph.style.textAlign='left';}
last=paragraph;}};var santize=function(e){clean(e);alignText(e);};element.addEventListener("change",function(){editor.content.innerHTML=markdown.render(element.value);alignText();});editor.content.setAttribute("placeholder",element.placeholder);editor.content.innerHTML=markdown.render(element.value);editor.content.tabIndex=0;alignText();editor.content.onkeydown=function preventTab(event){if(event.which===9){event.preventDefault();if(document.activeElement){var focussable=Array.prototype.filter.call(document.querySelectorAll('a:not([disabled]), button:not([disabled]), select:not([disabled]), input[type=text]:not([disabled]), input[type=checkbox]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'),function(element){return(element.offsetWidth>0||element.offsetHeight>0||element===document.activeElement);});var index=focussable.indexOf(document.activeElement);if(index>-1){if(event.shiftKey){var prevElement=focussable[index-1]||focussable[focussable.length-1];prevElement.focus();}else{var nextElement=focussable[index+1]||focussable[0];nextElement.focus();}}}}};div.addEventListener("paste",santize);div.addEventListener("drop",santize);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-remove",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){element.parentNode.removeChild(element);});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-run",repeat:false,controller:function(element,expression,container){let action=expression.parse(element.dataset["formsRun"]||'');element.addEventListener('click',function(){return container.path(action)();});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-select-all",controller:function(element){let select=document.createElement("button");let unselect=document.createElement("button");select.textContent='Select All';unselect.textContent='Unselect All';select.classList.add('link');select.classList.add('margin-top-tiny');select.classList.add('margin-start-small');select.classList.add('text-size-small');select.classList.add('pull-end');unselect.classList.add('link');unselect.classList.add('margin-top-tiny');unselect.classList.add('margin-start-small');unselect.classList.add('text-size-small');unselect.classList.add('pull-end');select.type='button';unselect.type='button';element.parentNode.insertBefore(select,element);element.parentNode.insertBefore(unselect,element);select.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=true;}})
unselect.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=false;}})}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-switch",controller:function(element){let input=window.document.createElement("input");input.type="checkbox";input.className="button switch";let syncA=function(){let value=input.checked?"true":"false"
unselect.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=false;}})}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-show-secret",controller:function(element,document){let button=document.createElement("a");button.type="button";button.className="icon-eye";button.innerHTML="show/hide";button.style.cursor="pointer";button.style.fontSize="10px";if(element.attributes.getNamedItem("data-forms-show-secret-above")){element.insertAdjacentElement("beforebegin",button);}else{element.parentNode.insertBefore(button,element.nextSibling);}
const toggle=function(event){switch(element.type){case"password":element.type="text";break;case"text":element.type="password";break;default:console.warn("data-forms-show-secret: element.type NOT text NOR password");}};button.addEventListener("click",toggle);},});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-switch",controller:function(element){let input=window.document.createElement("input");input.type="checkbox";input.className="button switch";let syncA=function(){let value=input.checked?"true":"false"
let old=element.value;element.value=value;if(value!==old){element.dispatchEvent(new Event('change'));}};let syncB=function(){input.checked=(element.value==="true");};input.addEventListener("input",syncA);input.addEventListener("change",syncA);element.addEventListener("input",syncB);element.addEventListener("change",syncB);syncA();element.parentNode.insertBefore(input,element);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-tags",controller:function(element){let array=[];let tags=window.document.createElement("div");let preview=window.document.createElement("ul");let add=window.document.createElement("input");let listen=function(event){if((event.key==="Enter"||event.key===" "||event.key==="Tab")&&add.value.length>0){array.push(add.value);add.value="";element.value=JSON.stringify(array);check();if(event.key!=="Tab"){event.preventDefault();}}
if((event.key==="Backspace"||event.key==="Delete")&&add.value===""){array.splice(-1,1);element.value=JSON.stringify(array);check();}
return false;};let check=function(){try{array=JSON.parse(element.value)||[];}catch(error){array=[];}

View file

@ -5,7 +5,7 @@
{http.addGlobalHeader('X-Appwrite-Locale',value);config.locale=value;return this;};let setMode=function(value)
{http.addGlobalHeader('X-Appwrite-Mode',value);config.mode=value;return this;};let http=function(document){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(Array.isArray(params[p])){for(let index=0;index<params[p].length;index++){let param=params[p][index];str.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}
return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key]={key:key.toLowerCase(),value:value.toLowerCase()};};let addGlobalParam=function(key,value){globalParams.push({key:key,value:value});};addGlobalHeader('x-sdk-version','appwrite:javascript:1.0.0');addGlobalHeader('content-type','');let call=function(method,path,headers={},params={},progress=null){let i;path=config.endpoint+path;if(-1===['GET','POST','PUT','DELETE','TRACE','HEAD','OPTIONS','CONNECT','PATCH'].indexOf(method)){throw new Error('var method must contain a valid HTTP method name');}
return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key]={key:key.toLowerCase(),value:value.toLowerCase()};};let addGlobalParam=function(key,value){globalParams.push({key:key,value:value});};addGlobalHeader('x-sdk-version','appwrite:web:1.0.0');addGlobalHeader('content-type','');let call=function(method,path,headers={},params={},progress=null){let i;path=config.endpoint+path;if(-1===['GET','POST','PUT','DELETE','TRACE','HEAD','OPTIONS','CONNECT','PATCH'].indexOf(method)){throw new Error('var method must contain a valid HTTP method name');}
if(typeof path!=='string'){throw new Error('var path must be of type string');}
if(typeof headers!=='object'){throw new Error('var headers must be of type object');}
for(i=0;i<globalParams.length;i++){path=addParam(path,globalParams[i].key,globalParams[i].value);}
@ -128,7 +128,7 @@ if(read){payload['read']=read;}
if(write){payload['write']=write;}
if(rules){payload['rules']=rules;}
return http.put(path,{'content-type':'application/json',},payload);},deleteCollection:function(collectionId){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},listDocuments:function(collectionId,filters=[],limit=25,offset=0,orderField='$id',orderType='ASC',orderCast='string',search=''){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},listDocuments:function(collectionId,filters=[],limit=25,offset=0,orderField='',orderType='ASC',orderCast='string',search=''){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/documents'.replace(new RegExp('{collectionId}','g'),collectionId);let payload={};if(filters){payload['filters']=filters;}
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
@ -162,18 +162,22 @@ let path='/database/collections/{collectionId}/documents/{documentId}'.replace(n
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
if(orderType){payload['orderType']=orderType;}
return http.get(path,{'content-type':'application/json',},payload);},create:function(name,env,vars=[],events=[],schedule='',timeout=15){if(name===undefined){throw new Error('Missing required parameter: "name"');}
return http.get(path,{'content-type':'application/json',},payload);},create:function(name,execute,env,vars=[],events=[],schedule='',timeout=15){if(name===undefined){throw new Error('Missing required parameter: "name"');}
if(execute===undefined){throw new Error('Missing required parameter: "execute"');}
if(env===undefined){throw new Error('Missing required parameter: "env"');}
let path='/functions';let payload={};if(name){payload['name']=name;}
if(execute){payload['execute']=execute;}
if(env){payload['env']=env;}
if(vars){payload['vars']=vars;}
if(events){payload['events']=events;}
if(schedule){payload['schedule']=schedule;}
if(timeout){payload['timeout']=timeout;}
return http.post(path,{'content-type':'application/json',},payload);},get:function(functionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},update:function(functionId,name,vars=[],events=[],schedule='',timeout=15){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},update:function(functionId,name,execute,vars=[],events=[],schedule='',timeout=15){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(name===undefined){throw new Error('Missing required parameter: "name"');}
if(execute===undefined){throw new Error('Missing required parameter: "execute"');}
let path='/functions/{functionId}'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(name){payload['name']=name;}
if(execute){payload['execute']=execute;}
if(vars){payload['vars']=vars;}
if(events){payload['events']=events;}
if(schedule){payload['schedule']=schedule;}
@ -184,9 +188,8 @@ let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}',
if(limit){payload['limit']=limit;}
if(offset){payload['offset']=offset;}
if(orderType){payload['orderType']=orderType;}
return http.get(path,{'content-type':'application/json',},payload);},createExecution:function(functionId,async=1){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(async){payload['async']=async;}
return http.post(path,{'content-type':'application/json',},payload);},getExecution:function(functionId,executionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
return http.get(path,{'content-type':'application/json',},payload);},createExecution:function(functionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace(new RegExp('{functionId}','g'),functionId);let payload={};return http.post(path,{'content-type':'application/json',},payload);},getExecution:function(functionId,executionId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(executionId===undefined){throw new Error('Missing required parameter: "executionId"');}
let path='/functions/{functionId}/executions/{executionId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{executionId}','g'),executionId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},updateTag:function(functionId,tag){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(tag===undefined){throw new Error('Missing required parameter: "tag"');}
@ -205,7 +208,7 @@ return http.post(path,{'content-type':'multipart/form-data',},payload);},getTag:
if(tagId===undefined){throw new Error('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},deleteTag:function(functionId,tagId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
if(tagId===undefined){throw new Error('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(functionId,range='last30'){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(functionId,range='30d'){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/usage'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(range){payload['range']=range;}
return http.get(path,{'content-type':'application/json',},payload);}};let health={get:function(){let path='/health';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getAntiVirus:function(){let path='/health/anti-virus';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCache:function(){let path='/health/cache';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getDB:function(){let path='/health/db';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueCertificates:function(){let path='/health/queue/certificates';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueFunctions:function(){let path='/health/queue/functions';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueLogs:function(){let path='/health/queue/logs';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueTasks:function(){let path='/health/queue/tasks';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueUsage:function(){let path='/health/queue/usage';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getQueueWebhooks:function(){let path='/health/queue/webhooks';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getStorageLocal:function(){let path='/health/storage/local';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getTime:function(){let path='/health/time';let payload={};return http.get(path,{'content-type':'application/json',},payload);}};let locale={get:function(){let path='/locale';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getContinents:function(){let path='/locale/continents';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountries:function(){let path='/locale/countries';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountriesEU:function(){let path='/locale/countries/eu';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCountriesPhones:function(){let path='/locale/countries/phones';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getCurrencies:function(){let path='/locale/currencies';let payload={};return http.get(path,{'content-type':'application/json',},payload);},getLanguages:function(){let path='/locale/languages';let payload={};return http.get(path,{'content-type':'application/json',},payload);}};let projects={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/projects';let payload={};if(search){payload['search']=search;}
if(limit){payload['limit']=limit;}
@ -329,7 +332,7 @@ if(httpUser){payload['httpUser']=httpUser;}
if(httpPass){payload['httpPass']=httpPass;}
return http.put(path,{'content-type':'application/json',},payload);},deleteTask:function(projectId,taskId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
if(taskId===undefined){throw new Error('Missing required parameter: "taskId"');}
let path='/projects/{projectId}/tasks/{taskId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{taskId}','g'),taskId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(projectId,range='last30'){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/tasks/{taskId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{taskId}','g'),taskId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},getUsage:function(projectId,range='30d'){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/usage'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(range){payload['range']=range;}
return http.get(path,{'content-type':'application/json',},payload);},listWebhooks:function(projectId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/webhooks'.replace(new RegExp('{projectId}','g'),projectId);let payload={};return http.get(path,{'content-type':'application/json',},payload);},createWebhook:function(projectId,name,events,url,security,httpUser='',httpPass=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');}
@ -387,9 +390,8 @@ if(background){payload['background']=background;}
if(output){payload['output']=output;}
payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{query.push(encodeURIComponent(p)+"="+encodeURIComponent(payload[p]));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');},getFileView:function(fileId,as=''){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace(new RegExp('{fileId}','g'),fileId);let payload={};if(as){payload['as']=as;}
payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');},getFileView:function(fileId){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace(new RegExp('{fileId}','g'),fileId);let payload={};payload['project']=config.project;payload['key']=config.key;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index<payload[p].length;index++){let param=payload[p][index];query.push(encodeURIComponent(p+'[]')+"="+encodeURIComponent(param));}}
else{query.push(encodeURIComponent(p)+"="+encodeURIComponent(payload[p]));}}
query=query.join("&");return config.endpoint+path+((query)?'?'+query:'');}};let teams={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/teams';let payload={};if(search){payload['search']=search;}
if(limit){payload['limit']=limit;}

View file

@ -376,7 +376,8 @@ score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){va
if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';}
else{paragraph.style.direction='ltr';paragraph.style.textAlign='left';}
last=paragraph;}};var santize=function(e){clean(e);alignText(e);};element.addEventListener("change",function(){editor.content.innerHTML=markdown.render(element.value);alignText();});editor.content.setAttribute("placeholder",element.placeholder);editor.content.innerHTML=markdown.render(element.value);editor.content.tabIndex=0;alignText();editor.content.onkeydown=function preventTab(event){if(event.which===9){event.preventDefault();if(document.activeElement){var focussable=Array.prototype.filter.call(document.querySelectorAll('a:not([disabled]), button:not([disabled]), select:not([disabled]), input[type=text]:not([disabled]), input[type=checkbox]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'),function(element){return(element.offsetWidth>0||element.offsetHeight>0||element===document.activeElement);});var index=focussable.indexOf(document.activeElement);if(index>-1){if(event.shiftKey){var prevElement=focussable[index-1]||focussable[focussable.length-1];prevElement.focus();}else{var nextElement=focussable[index+1]||focussable[0];nextElement.focus();}}}}};div.addEventListener("paste",santize);div.addEventListener("drop",santize);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-remove",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){element.parentNode.removeChild(element);});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-run",repeat:false,controller:function(element,expression,container){let action=expression.parse(element.dataset["formsRun"]||'');element.addEventListener('click',function(){return container.path(action)();});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-select-all",controller:function(element){let select=document.createElement("button");let unselect=document.createElement("button");select.textContent='Select All';unselect.textContent='Unselect All';select.classList.add('link');select.classList.add('margin-top-tiny');select.classList.add('margin-start-small');select.classList.add('text-size-small');select.classList.add('pull-end');unselect.classList.add('link');unselect.classList.add('margin-top-tiny');unselect.classList.add('margin-start-small');unselect.classList.add('text-size-small');unselect.classList.add('pull-end');select.type='button';unselect.type='button';element.parentNode.insertBefore(select,element);element.parentNode.insertBefore(unselect,element);select.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=true;}})
unselect.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=false;}})}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-switch",controller:function(element){let input=window.document.createElement("input");input.type="checkbox";input.className="button switch";let syncA=function(){let value=input.checked?"true":"false"
unselect.addEventListener('click',function(){let checkboxes=document.querySelectorAll("input[type='checkbox']");for(var i=0;i<checkboxes.length;i++){checkboxes[i].checked=false;}})}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-show-secret",controller:function(element,document){let button=document.createElement("a");button.type="button";button.className="icon-eye";button.innerHTML="show/hide";button.style.cursor="pointer";button.style.fontSize="10px";if(element.attributes.getNamedItem("data-forms-show-secret-above")){element.insertAdjacentElement("beforebegin",button);}else{element.parentNode.insertBefore(button,element.nextSibling);}
const toggle=function(event){switch(element.type){case"password":element.type="text";break;case"text":element.type="password";break;default:console.warn("data-forms-show-secret: element.type NOT text NOR password");}};button.addEventListener("click",toggle);},});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-switch",controller:function(element){let input=window.document.createElement("input");input.type="checkbox";input.className="button switch";let syncA=function(){let value=input.checked?"true":"false"
let old=element.value;element.value=value;if(value!==old){element.dispatchEvent(new Event('change'));}};let syncB=function(){input.checked=(element.value==="true");};input.addEventListener("input",syncA);input.addEventListener("change",syncA);element.addEventListener("input",syncB);element.addEventListener("change",syncB);syncA();element.parentNode.insertBefore(input,element);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-tags",controller:function(element){let array=[];let tags=window.document.createElement("div");let preview=window.document.createElement("ul");let add=window.document.createElement("input");let listen=function(event){if((event.key==="Enter"||event.key===" "||event.key==="Tab")&&add.value.length>0){array.push(add.value);add.value="";element.value=JSON.stringify(array);check();if(event.key!=="Tab"){event.preventDefault();}}
if((event.key==="Backspace"||event.key==="Delete")&&add.value===""){array.splice(-1,1);element.value=JSON.stringify(array);check();}
return false;};let check=function(){try{array=JSON.parse(element.value)||[];}catch(error){array=[];}

View file

@ -139,7 +139,7 @@
globalParams.push({key: key, value: value});
};
addGlobalHeader('x-sdk-version', 'appwrite:javascript:1.0.0');
addGlobalHeader('x-sdk-version', 'appwrite:web:1.0.0');
addGlobalHeader('content-type', '');
/**
@ -312,8 +312,8 @@
* Use this endpoint to allow a new user to register a new account in your
* project. After the user registration completes successfully, you can use
* the [/account/verfication](/docs/client/account#createVerification) route
* to start verifying the user email address. To allow your new user to login
* to his new account, you need to create a new [account
* to start verifying the user email address. To allow the new user to login
* to their new account, you need to create a new [account
* session](/docs/client/account#createSession).
*
* @param {string} email
@ -678,7 +678,7 @@
/**
* Create Account Session
*
* Allow the user to login into his account by providing a valid email and
* Allow the user to login into their account by providing a valid email and
* password combination. This route will create a new session for the user.
*
* @param {string} email
@ -736,7 +736,7 @@
/**
* Create Account Session with OAuth2
*
* Allow the user to login to his account using the OAuth2 provider of his
* Allow the user to login to their account using the OAuth2 provider of their
* choice. Each OAuth2 provider should be enabled from the Appwrite console
* first. Use the success and failure arguments to provide a redirect URL's
* back to your app when login is completed.
@ -796,9 +796,9 @@
/**
* Delete Account Session
*
* Use this endpoint to log out the currently logged in user from all his
* account sessions across all his different devices. When using the option id
* argument, only the session unique ID provider will be deleted.
* Use this endpoint to log out the currently logged in user from all their
* account sessions across all of their different devices. When using the
* option id argument, only the session unique ID provider will be deleted.
*
* @param {string} sessionId
* @throws {Error}
@ -829,7 +829,7 @@
* should redirect the user back to your app and allow you to complete the
* verification process by verifying both the **userId** and **secret**
* parameters. Learn more about how to [complete the verification
* process](/docs/client/account#updateAccountVerification).
* process](/docs/client/account#updateVerification).
*
* Please note that in order to avoid a [Redirect
* Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
@ -1026,8 +1026,9 @@
/**
* Get Favicon
*
* Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote
* Use this endpoint to fetch the favorite icon (AKA favicon) of any remote
* website URL.
*
*
* @param {string} url
* @throws {Error}
@ -1546,7 +1547,7 @@
* @throws {Error}
* @return {Promise}
*/
listDocuments: function(collectionId, filters = [], limit = 25, offset = 0, orderField = '$id', orderType = 'ASC', orderCast = 'string', search = '') {
listDocuments: function(collectionId, filters = [], limit = 25, offset = 0, orderField = '', orderType = 'ASC', orderCast = 'string', search = '') {
if(collectionId === undefined) {
throw new Error('Missing required parameter: "collectionId"');
}
@ -1747,7 +1748,7 @@
* Delete Document
*
* Delete document by its unique ID. This endpoint deletes only the parent
* documents, his attributes and relations to other documents. Child documents
* documents, its attributes and relations to other documents. Child documents
* **will not** be deleted.
*
* @param {string} collectionId
@ -1820,6 +1821,7 @@
*
*
* @param {string} name
* @param {string[]} execute
* @param {string} env
* @param {object} vars
* @param {string[]} events
@ -1828,11 +1830,15 @@
* @throws {Error}
* @return {Promise}
*/
create: function(name, env, vars = [], events = [], schedule = '', timeout = 15) {
create: function(name, execute, env, vars = [], events = [], schedule = '', timeout = 15) {
if(name === undefined) {
throw new Error('Missing required parameter: "name"');
}
if(execute === undefined) {
throw new Error('Missing required parameter: "execute"');
}
if(env === undefined) {
throw new Error('Missing required parameter: "env"');
}
@ -1845,6 +1851,10 @@
payload['name'] = name;
}
if(execute) {
payload['execute'] = execute;
}
if(env) {
payload['env'] = env;
}
@ -1900,6 +1910,7 @@
*
* @param {string} functionId
* @param {string} name
* @param {string[]} execute
* @param {object} vars
* @param {string[]} events
* @param {string} schedule
@ -1907,7 +1918,7 @@
* @throws {Error}
* @return {Promise}
*/
update: function(functionId, name, vars = [], events = [], schedule = '', timeout = 15) {
update: function(functionId, name, execute, vars = [], events = [], schedule = '', timeout = 15) {
if(functionId === undefined) {
throw new Error('Missing required parameter: "functionId"');
}
@ -1916,6 +1927,10 @@
throw new Error('Missing required parameter: "name"');
}
if(execute === undefined) {
throw new Error('Missing required parameter: "execute"');
}
let path = '/functions/{functionId}'.replace(new RegExp('{functionId}', 'g'), functionId);
let payload = {};
@ -1924,6 +1939,10 @@
payload['name'] = name;
}
if(execute) {
payload['execute'] = execute;
}
if(vars) {
payload['vars'] = vars;
}
@ -2017,11 +2036,10 @@
*
*
* @param {string} functionId
* @param {number} async
* @throws {Error}
* @return {Promise}
*/
createExecution: function(functionId, async = 1) {
createExecution: function(functionId) {
if(functionId === undefined) {
throw new Error('Missing required parameter: "functionId"');
}
@ -2030,10 +2048,6 @@
let payload = {};
if(async) {
payload['async'] = async;
}
return http
.post(path, {
'content-type': 'application/json',
@ -2249,7 +2263,7 @@
* @throws {Error}
* @return {Promise}
*/
getUsage: function(functionId, range = 'last30') {
getUsage: function(functionId, range = '30d') {
if(functionId === undefined) {
throw new Error('Missing required parameter: "functionId"');
}
@ -3724,7 +3738,7 @@
* @throws {Error}
* @return {Promise}
*/
getUsage: function(projectId, range = 'last30') {
getUsage: function(projectId, range = '30d') {
if(projectId === undefined) {
throw new Error('Missing required parameter: "projectId"');
}
@ -4271,11 +4285,10 @@
* method but returns with no 'Content-Disposition: attachment' header.
*
* @param {string} fileId
* @param {string} as
* @throws {Error}
* @return {string}
*/
getFileView: function(fileId, as = '') {
getFileView: function(fileId) {
if(fileId === undefined) {
throw new Error('Missing required parameter: "fileId"');
}
@ -4284,10 +4297,6 @@
let payload = {};
if(as) {
payload['as'] = as;
}
payload['project'] = config.project;
payload['key'] = config.key;
@ -4594,7 +4603,7 @@
*
* This endpoint allows a user to leave a team or for a team owner to delete
* the membership of any other team member. You can also use this endpoint to
* delete a user membership even if he didn't accept it.
* delete a user membership even if it is not accepted.
*
* @param {string} teamId
* @param {string} inviteId
@ -4624,8 +4633,8 @@
* Update Team Membership Status
*
* Use this endpoint to allow a user to accept an invitation to join a team
* after he is being redirected back to your app from the invitation email he
* was sent.
* after being redirected back to your app from the invitation email recieved
* by the user.
*
* @param {string} teamId
* @param {string} inviteId

View file

@ -0,0 +1,38 @@
(function (window) {
"use strict";
window.ls.container.get("view").add({
selector: "data-forms-show-secret",
controller: function (element, document) {
let button = document.createElement("a");
button.type = "button";
button.className = "icon-eye";
button.innerHTML = "show/hide";
button.style.cursor = "pointer";
button.style.fontSize = "10px";
if (element.attributes.getNamedItem("data-forms-show-secret-above")) {
element.insertAdjacentElement("beforebegin", button);
} else {
element.parentNode.insertBefore(button, element.nextSibling);
}
const toggle = function (event) {
switch (element.type) {
case "password":
element.type = "text";
break;
case "text":
element.type = "password";
break;
default:
console.warn(
"data-forms-show-secret: element.type NOT text NOR password"
);
}
};
button.addEventListener("click", toggle);
},
});
})(window);

View file

@ -82,7 +82,7 @@ class Authorization extends Validator
}
}
$this->message = 'User is missing '.$this->action.' for "'.$permission.'" permission. Only this scopes "'.\json_encode(self::getRoles()).'" is given and only this are allowed "'.\json_encode($permissions[$this->action]).'".';
$this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only this scopes "'.\json_encode(self::getRoles()).'" are given and only this are allowed "'.\json_encode($permissions[$this->action]).'".';
return false;
}

View file

@ -57,7 +57,7 @@ class Permissions extends Validator
}
foreach ($value as $action => $roles) {
if (!\in_array($action, ['read', 'write'])) {
if (!\in_array($action, ['read', 'write', 'execute'])) {
$this->message = 'Unknown action ("'.$action.'")';
return false;
@ -65,7 +65,7 @@ class Permissions extends Validator
foreach ($roles as $role) {
if (!\is_string($role)) {
$this->message = 'Permissions role must be a string';
$this->message = 'Permissions role must be of type string.';
return false;
}

View file

@ -6,6 +6,32 @@ use Resque;
class Event
{
const DELETE_QUEUE_NAME = 'v1-deletes';
const DELETE_CLASS_NAME = 'DeletesV1';
const AUDITS_QUEUE_NAME = 'v1-audits';
const AUDITS_CLASS_NAME = 'AuditsV1';
const USAGE_QUEUE_NAME = 'v1-usage';
const USAGE_CLASS_NAME = 'UsageV1';
const MAILS_QUEUE_NAME = 'v1-mails';
const MAILS_CLASS_NAME = 'MailsV1';
const FUNCTIONS_QUEUE_NAME = 'v1-functions';
const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
const WEBHOOK_CLASS_NAME = 'WebhooksV1';
const TASK_QUEUE_NAME = 'v1-tasks';
const TASK_CLASS_NAME = 'TasksV1';
const CERTIFICATES_QUEUE_NAME = 'v1-certificates';
const CERTIFICATES_CLASS_NAME = 'CertificatesV1';
/**
* @var string
*/

View file

@ -15,6 +15,12 @@ class Func extends Model
'description' => 'Function ID.',
'example' => '5e5ea5c16897e',
])
->addRule('$permissions', [
'type' => Response::MODEL_PERMISSIONS,
'description' => 'Function permissions.',
'example' => new \stdClass,
'array' => false,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Function name.',

View file

@ -75,6 +75,8 @@ trait ProjectCustom
'files.write',
'functions.read',
'functions.write',
'execution.read',
'execution.write',
'locale.read',
'avatars.read',
'health.read',

View file

@ -49,8 +49,6 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 409);
sleep(5);
return [
'id' => $id,
'email' => $email,
@ -64,7 +62,6 @@ trait AccountBase
*/
public function testCreateAccountSession($data):array
{
sleep(10);
$email = $data['email'] ?? '';
$password = $data['password'] ?? '';
@ -291,7 +288,7 @@ trait AccountBase
$this->assertNotEmpty($response['body']['logs']);
$this->assertCount(2, $response['body']['logs']);
$this->assertEquals('account.sessions.create', $response['body']['logs'][0]['event']);
$this->assertContains($response['body']['logs'][0]['event'], ['account.create', 'account.sessions.create']);
$this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body']['logs'][0]['time']);
@ -301,7 +298,7 @@ trait AccountBase
$this->assertEquals('browser', $response['body']['logs'][0]['clientType']);
$this->assertEquals('Chrome', $response['body']['logs'][0]['clientName']);
$this->assertEquals('CH', $response['body']['logs'][0]['clientCode']); // FIXME (v1) key name should be camelcase
$this->assertEquals('CH', $response['body']['logs'][0]['clientCode']);
$this->assertEquals('70.0', $response['body']['logs'][0]['clientVersion']);
$this->assertEquals('Blink', $response['body']['logs'][0]['clientEngine']);
@ -313,7 +310,7 @@ trait AccountBase
$this->assertEquals('--', $response['body']['logs'][0]['countryCode']);
$this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']);
$this->assertEquals('account.create', $response['body']['logs'][1]['event']);
$this->assertContains($response['body']['logs'][1]['event'], ['account.create', 'account.sessions.create']);
$this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body']['logs'][1]['time']);
@ -323,7 +320,7 @@ trait AccountBase
$this->assertEquals('browser', $response['body']['logs'][1]['clientType']);
$this->assertEquals('Chrome', $response['body']['logs'][1]['clientName']);
$this->assertEquals('CH', $response['body']['logs'][1]['clientCode']); // FIXME (v1) key name should be camelcase
$this->assertEquals('CH', $response['body']['logs'][1]['clientCode']);
$this->assertEquals('70.0', $response['body']['logs'][1]['clientVersion']);
$this->assertEquals('Blink', $response['body']['logs'][1]['clientEngine']);

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Functions;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@ -40,4 +41,78 @@ class FunctionsCustomClientTest extends Scope
return [];
}
public function testCreateExecution():array
{
/**
* Test for SUCCESS
*/
$function = $this->client->call(Client::METHOD_POST, '/functions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'name' => 'Test',
'execute' => ['user:'.$this->getUser()['$id']],
'env' => 'php-7.4',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
'funcKey3' => 'funcValue3',
],
'events' => [
'account.create',
'account.delete',
],
'schedule' => '* * * * *',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$tag = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/tags', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'command' => 'php function.php',
'code' => new CURLFile(realpath(__DIR__ . '/../../../resources/functions/php.tar.gz'), 'application/x-gzip', 'php-fx.tar.gz'),
]);
$tagId = $tag['body']['$id'] ?? '';
$this->assertEquals(201, $tag['headers']['status-code']);
$function = $this->client->call(Client::METHOD_PATCH, '/functions/'.$function['body']['$id'].'/tag', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'tag' => $tagId,
]);
$this->assertEquals(200, $function['headers']['status-code']);
$execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'async' => 1,
]);
$this->assertEquals(401, $execution['headers']['status-code']);
$execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => 1,
]);
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
return [];
}
}

View file

@ -591,14 +591,15 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
sleep(15);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
if($executions['body']['executions'][0]['status'] === 'failed') {
if($executions['body']['executions'][0]['status'] !== 'completed') {
var_dump($env);
var_dump($executions['body']['executions'][0]);
}
@ -680,6 +681,7 @@ class FunctionsCustomServerTest extends Scope
]);
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
sleep(15);

View file

@ -1,3 +1,3 @@
<?php
sleep(10);
sleep(5);